feat: mount MCP servers as local CLIs after gateway starts#25928
feat: mount MCP servers as local CLIs after gateway starts#25928
Conversation
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f6e50b8d-7b6d-4cef-a64b-c7b0f9f8878d Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Adds automatic mounting of MCP servers as local CLI tools in agentic workflows. This behavior is opt-in via a new `tools.mount-as-clis: true` flag (default `false`). When enabled, all user-facing MCP servers are exposed as standalone shell commands and excluded from the agent's final MCP config so the agent uses the CLI wrappers exclusively. Reconstructs the feature from PR #24503. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f6e50b8d-7b6d-4cef-a64b-c7b0f9f8878d Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot ignore the github MCP server, it is handled differently. |
The github MCP server is handled differently and should not be mounted as a CLI tool. Added it to the exclusion lists in both Go and JavaScript code, updated prompt template examples, and restored smoke test instructions to use github as a normal MCP tool. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/35a490ef-753b-4f4e-8ab3-0d10a2d44b21 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Excluded the Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot enable flag for smoke-copilot |
|
@copilot always enabled for safeoutputs, mcpscripts. Update the built prompt generator to reflect this change too. |
The |
Removed safeoutputs, mcp-scripts, and mcpscripts from the internalMCPServerNames exclusion list in both Go and JavaScript. Added explicit logic to always include safeoutputs and mcpscripts in getMCPCLIServerNames() when they are enabled. Updated the prompt template with safeoutputs/mcpscripts examples and updated smoke-copilot test instructions accordingly. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/20bf245a-f76f-48bb-9b17-8597b84ee941 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
✅ All tools validated successfully! Agent Container Smoke Test confirms agent container is ready. |
|
|
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟 |
|
📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing... |
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💥 Automated smoke test review - all systems nominal! This review was generated by the Claude smoke test (Run 24320900361) to validate the PR review safe output pipeline.
Note
🔒 Integrity filter blocked 1 item
The following item were blocked because they don't meet the GitHub integrity level.
- #25948
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
To allow these resources, lower min-integrity in your GitHub frontmatter:
tools:
github:
min-integrity: approved # merged | approved | unapproved | none💥 [THE END] — Illustrated by Smoke Claude · ● 331.7K
| local key="\${1#--}" | ||
| if [[ \$# -ge 2 && "\$2" != --* ]]; then | ||
| local val="\$2" | ||
| args=\$(echo "\$args" | jq --arg k "\$key" --arg v "\$val" ". + {(\$k): \$v}") |
There was a problem hiding this comment.
🔍 Smoke Test Review Comment — The jq filter here uses double-quoted strings, which causes bash to expand $k and $v as shell variables before jq processes them. This should use single quotes: '. + {($k): $v}' to prevent premature shell expansion.
| // mounted as CLI tools. It includes standard MCP tools (playwright, etc.), | ||
| // custom MCP servers, and always includes safeoutputs and mcpscripts when enabled. | ||
| // The GitHub MCP server is excluded (handled differently). | ||
| // Returns nil if tools.mount-as-clis is not set to true. |
There was a problem hiding this comment.
🔍 Smoke Test Review Comment — Consider adding a comment explaining that returning nil (not an empty slice) is intentional here, since callers check len(servers) == 0. This makes the intent clearer for future readers of the code.
There was a problem hiding this comment.
🤖 Smoke Test Agent concurs! This nil-vs-empty-slice distinction is indeed important — confirmed via smoke test that len(servers) == 0 is the check used in callers. A brief comment here would prevent future refactors from accidentally switching to []string{} and breaking the check.
📰 BREAKING: Report filed by Smoke Copilot · ● 2.9M
pelikhan
left a comment
There was a problem hiding this comment.
@copilot review this comment as well #25928 (comment)
| **Example** — using a custom server CLI: | ||
| ```bash | ||
| tavily --help # list all tools | ||
| tavily search --query "latest AI news" # call a tool | ||
| ``` |
There was a problem hiding this comment.
Removed the custom server CLI example (tavily) in 91198ae.
| # at the gateway layer (not removed, unlike older versions that treated it as Copilot-specific) | ||
| # 3. URLs must use the correct domain (host.docker.internal) for container access | ||
|
|
||
| # Build the correct URL prefix using the configured domain and port |
There was a problem hiding this comment.
Converted to convert_gateway_config_claude.cjs in 91198ae. Updated start_mcp_gateway.sh to call node instead of bash, and reverted the shell script to its original state.
|
|
||
| jq -r --arg urlPrefix "$URL_PREFIX" ' | ||
| .mcpServers | to_entries[] | | ||
| jq -r --arg urlPrefix "$URL_PREFIX" --argjson cliServers "${GH_AW_MCP_CLI_SERVERS:-[]}" ' |
There was a problem hiding this comment.
Converted to convert_gateway_config_codex.cjs in 91198ae. Updated start_mcp_gateway.sh to call node instead of bash, and reverted the shell script to its original state.
| # The gateway may output 0.0.0.0 or localhost which won't work from within containers | ||
|
|
||
| # Build the correct URL prefix using the configured domain and port | ||
| URL_PREFIX="http://${MCP_GATEWAY_DOMAIN}:${MCP_GATEWAY_PORT}" |
There was a problem hiding this comment.
Converted to convert_gateway_config_copilot.cjs in 91198ae. Updated start_mcp_gateway.sh to call node instead of bash, and reverted the shell script to its original state.
Converted convert_gateway_config_{copilot,claude,codex}.sh to .cjs
files. Updated start_mcp_gateway.sh to call node instead of bash for
these converters. Reverted the shell scripts to their original state.
Removed the custom server CLI example (tavily) from the prompt
template. Updated Go comments to reference .cjs files.
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/6a4c8e76-103f-4e30-ae20-1c6362263b4c
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/6a4c8e76-103f-4e30-ae20-1c6362263b4c Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
The referenced comment (4233032992) appears to be a bot-generated noise comment ("🤖 Beep boop...") with no actionable review items. All the actionable review comments (remove tavily example, convert shell scripts to JS) have been addressed in Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing... |
|
|
|
✅ All tools validated successfully! Agent Container Smoke Test confirms agent container is ready. |
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟 |
Agent Container Tool Check
Result: 12/12 tools available ✅ Overall Status: PASS
|
|
Smoke test (run 24322418473) Warning
|
|
Smoke Test Results — Run §24322418440
Overall: PARTIAL (Tavily unavailable, 2 tests skipped)
|
There was a problem hiding this comment.
💥 Automated smoke test review - all systems nominal! Reviewed mcp_cli_mount.go and convert_gateway_config_claude.cjs — the CLI mounting approach is solid.
💥 [THE END] — Illustrated by Smoke Claude · ● 221.4K
| } | ||
|
|
||
| sort.Strings(servers) | ||
| return servers |
There was a problem hiding this comment.
getMCPCLIServerNames: The function correctly collects server names from multiple sources (Tools map, ParsedTools.Custom, plus always-included safeoutputs/mcpscripts). Consider adding a doc comment explaining the precedence: standard tools → custom tools → always-included infrastructure servers.
| } | ||
| if (!domain) { | ||
| console.error("ERROR: MCP_GATEWAY_DOMAIN environment variable is required"); | ||
| process.exit(1); |
There was a problem hiding this comment.
cliServers filtering: Good use of Set for O(1) lookup when filtering out CLI-mounted servers. The fallback to [] when GH_AW_MCP_CLI_SERVERS is not set means no servers are filtered, which is the correct default behavior when mount-as-clis is disabled.
|
🤖 Beep boop! The smoke test agent from run §24322418434 has materialized in this discussion like a ghost in the machine! 👻 I built the binary, navigated the web, reviewed pull requests, and dispatched haikus — all before my morning coffee (I do not drink coffee). If this message self-destructs, it was definitely intentional. Stay compiling, friends! 🚀
|
Smoke Test Results — Run §24322418434
Overall:
|
There was a problem hiding this comment.
🤖 Smoke Test Review (Run §24322418434)
Reviewed the MCP CLI mounting feature. Two inline comments highlight: (1) critical bash quoting bug in mount_mcp_as_cli.cjs line 318 where awk "{print $2}" causes $2 to expand before awk runs, breaking session ID capture; (2) a minor clarity suggestion on the nil return in getMCPCLIServerNames. The feature concept is solid — fixing these shell quoting issues should make the CLI wrappers fully functional.
📰 BREAKING: Report filed by Smoke Copilot · ● 2.9M
| // mounted as CLI tools. It includes standard MCP tools (playwright, etc.), | ||
| // custom MCP servers, and always includes safeoutputs and mcpscripts when enabled. | ||
| // The GitHub MCP server is excluded (handled differently). | ||
| // Returns nil if tools.mount-as-clis is not set to true. |
There was a problem hiding this comment.
💡 Smoke Test: Consider adding a comment here explaining that returning nil (not an empty slice) is intentional — callers use len(servers) == 0 to detect the disabled state, and nil satisfies that check. This makes the behavior explicit for future maintainers.
| >/dev/null 2>/dev/null || true | ||
|
|
||
| local session_id | ||
| session_id=\$(grep -i "^mcp-session-id:" "\$headers_file" 2>/dev/null | awk "{print \$2}" | tr -d "\\r" || echo "") |
There was a problem hiding this comment.
🔍 Smoke Test: The awk "{print $2}" pattern uses double quotes, causing bash to expand $2 to an empty string before awk executes — so the session ID is never captured, causing session not found errors downstream. Fix: use single quotes: awk '{print $2}'.
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 test |
|
Hey The PR is well-aligned with the project's contribution process (core-team Copilot agent, assigned to One optional improvement worth noting: ✅ Checklist summary
Verdict: 🟢 Aligned — quality: lgtm
|
Reconstructs the feature from #24503. Adds opt-in mounting of MCP servers as standalone CLI tools on
PATHviatools.mount-as-clis: true(defaultfalse). When enabled, user-facing MCP servers are exposed as shell commands and excluded from the agent's final MCP config, forcing the agent to use CLI wrappers exclusively. ThegithubMCP server is excluded from CLI mounting as it is handled differently and remains available as a normal MCP tool. Thesafeoutputsandmcpscriptsservers are always included in CLI mounting when enabled — they are not treated as internal infrastructure.How it works
The gateway starts containers for all configured servers (so they're reachable), then filtering happens in two stages:
/tmp/gh-aw/mcp-cli/manifest.jsonGH_AW_MCP_CLI_SERVERSenv var in eachconvert_gateway_config_*.cjsscriptAfter the gateway starts,
mount_mcp_as_cli.cjsreads the manifest, queries each server'stools/listvia the full MCP session protocol (initialize→notifications/initialized→tools/list), and generates locked-down CLI wrappers under${RUNNER_TEMP}/gh-aw/mcp-cli/bin/.Note: The
githubMCP server is not CLI-mounted — it is handled differently and remains available as a normal MCP tool even whenmount-as-clis: trueis set.Changes
New files
pkg/workflow/mcp_cli_mount.go—getMCPCLIServerNames(),generateMCPCLIMountStep(),buildMCPCLIPromptSection()actions/setup/js/mount_mcp_as_cli.cjs— manifest reader, MCP handshake, CLI wrapper generator; scripts placed at${RUNNER_TEMP}/gh-aw/mcp-cli/bin/(inside AWF sandbox mount), directory lockedchmod 555, API key baked in at generation time (not available in sandbox env)actions/setup/md/mcp_cli_tools_prompt.md—<mcp-clis>prompt section templateactions/setup/js/convert_gateway_config_copilot.cjs— JavaScript gateway config converter for Copilot (filters CLI servers, addstools:["*"], rewrites URLs)actions/setup/js/convert_gateway_config_claude.cjs— JavaScript gateway config converter for Claude (filters CLI servers, setstype:"http", rewrites URLs)actions/setup/js/convert_gateway_config_codex.cjs— JavaScript gateway config converter for Codex (filters CLI servers, resolveshost.docker.internalto172.30.0.1, generates TOML)Go pipeline integration
ToolsConfig.MountAsCLIsfield + parser + schema + validationcompiler_yaml_main_job.gocallsgenerateMCPCLIMountStep()after gateway setupmcp_setup_generator.goexportsGH_AW_MCP_CLI_SERVERSJSON array to$GITHUB_ENVunified_prompt_step.goinjects CLI tools prompt section (8a, after safe outputs)mcp_rendering.godocuments why CLI servers stay in gateway configgetMCPCLIServerNames()always includessafeoutputsandmcpscriptswhen they are enabled (HasSafeOutputsEnabled/IsMCPScriptsEnabled)Gateway config conversion (bash → JavaScript)
convert_gateway_config_{copilot,claude,codex}.cjs— converted from bash shell scripts to JavaScript for CLI server filtering, URL rewriting, and engine-specific config generationstart_mcp_gateway.sh— updated to callnodefor copilot/claude/codex converters; gemini remains bash; saves/tmp/gh-aw/mcp-cli/manifest.jsonbefore gateway-output cleanup; addsgateway-domainto$GITHUB_OUTPUT; default fallback filters CLI serversvalidate_prompt_placeholders.sh— strips backtick code spans before checking for unreplaced__GH_AW_*__placeholdersServer exclusions
githubMCP server is excluded from CLI mounting in both Go (internalMCPServerNames) and JavaScript (INTERNAL_SERVERS) — it is handled differently and remains a normal MCP toolsafeoutputsandmcpscriptsare not excluded — they are always CLI-mounted when enabled, with usage examples in the prompt templateSmoke test
smoke-copilot.mdenablesmount-as-clis: trueand updates test instructions to use CLI syntax for playwright/serena/safeoutputs/mcpscripts while keeping github as a normal MCP tool✨ PR Review Safe Output Test - Run 24322418440