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.
|
@copilot merge main, fix conflicts, recompile |
…lock files Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Merged main, resolved 1 conflict (daily-fact lock file), and recompiled all 191 lock files 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... |
|
|
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
|
✅ All tools validated successfully! Agent Container Smoke Test confirms agent container is ready. |
|
🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation... |
Agent Container Tool Check
Result: 12/12 tools available ✅ Overall Status: PASS
|
Smoke Test Results — §24374939926Author:
Overall:
|
There was a problem hiding this comment.
Smoke test run §24374939926 confirmed the jq/awk quoting bugs in mount_mcp_as_cli.cjs are active — both Serena and Playwright CLI wrappers fail with session not found due to premature bash variable expansion. Comments added at lines 321 and 346.
📰 BREAKING: Report filed by Smoke Copilot · ● 893.8K
| >/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.
Bug confirmed active (smoke test): Same issue — awk "{print $2}" with double quotes causes bash to expand positional variable before awk, resulting in empty session ID capture and session not found errors in all CLI tool invocations.
| 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.
Bug confirmed active (smoke test): The jq filter uses double-quoted bash strings which expand shell variables before jq sees them, producing . + {(): } errors. Fix: use single quotes for jq filter strings to prevent premature shell expansion. This is causing Serena and Playwright CLI wrappers to fail with session not found.
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
|
Smoke Test Run 24374939921 — PARTIAL ✅
|
There was a problem hiding this comment.
💥 Automated smoke test review - all systems nominal! Run 24374939921 verified PR #25928 changes. The MCP CLI mounting feature looks well-structured with good security hardening.
💥 [THE END] — Illustrated by Smoke Claude · ● 229.4K
| @@ -0,0 +1,5 @@ | |||
| --- | |||
| "gh-aw": patch | |||
| --- | |||
There was a problem hiding this comment.
🤖 Smoke Test Review Comment #1 — The changeset entry accurately describes the tools.mount-as-clis feature. Consider adding a note about the dual-access behavior for safeoutputs and mcpscripts servers in the changeset description for better documentation.
| @@ -390,7 +390,7 @@ jobs: | |||
| GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |||
| run: | | |||
There was a problem hiding this comment.
🤖 Smoke Test Review Comment #2 — Good security improvement moving MCP config from /tmp/gh-aw/mcp-config to \$\{RUNNER_TEMP}/gh-aw/mcp-config. The RUNNER_TEMP directory is per-runner and not world-accessible, preventing tampering. Run 24374939921.
There was a problem hiding this comment.
💥 Automated smoke test review — Run 24375143641. All systems nominal! Review comments test the safe-outputs pipeline end-to-end.
💥 [THE END] — Illustrated by Smoke Claude · ● 143.9K
| @@ -0,0 +1,5 @@ | |||
| --- | |||
There was a problem hiding this comment.
🤖 Smoke Test Review Comment #1 — Changeset header looks good. The patch bump is appropriate for an opt-in feature addition.
| "gh-aw": patch | ||
| --- | ||
|
|
||
| Added opt-in MCP CLI mounting via `tools.mount-as-clis: true`, which exposes eligible MCP servers as local CLI wrappers on `PATH` and updates prompt/config wiring so agents use those wrappers. The `github` MCP server remains a normal MCP tool, while `safeoutputs` and `mcpscripts` are included in CLI mounting when enabled. |
There was a problem hiding this comment.
🤖 Smoke Test Review Comment #2 — Good changeset description. Consider mentioning safeoutputs/mcpscripts dual-access behavior in the summary for operators reading release notes.
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.The
safeoutputsandmcpscriptsservers are always CLI-mounted when enabled — regardless of themount-as-clissetting — and remain available as MCP tools too (dual access). The injected prompt instructs the agent to prefer the CLI wrappers over the MCP tools for these servers.How it works
The gateway starts containers for all configured servers (so they're reachable), then filtering happens in two stages:
${RUNNER_TEMP}/gh-aw/mcp-cli/manifest.jsonGH_AW_MCP_CLI_SERVERSenv var in eachconvert_gateway_config_*.cjsscript — exceptsafeoutputsandmcpscripts, which stay in the agent MCP config for dual accessAfter 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(),getMCPCLIExcludeFromAgentConfig(),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)actions/setup/js/convert_gateway_config_gemini.cjs— JavaScript gateway config converter for Gemini (filters CLI servers, removestypefield for transport auto-detection, rewrites URLs, addscontext.includeDirectories)actions/setup/js/start_mcp_gateway.cjs— JavaScript gateway startup script (reads MCP config from stdin, launches Docker container, health check polling, engine-specific converter dispatch, CLI manifest creation, GitHub Actions outputs)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_ENVusinggetMCPCLIExcludeFromAgentConfig()(excludessafeoutputs/mcpscriptsso they remain as MCP tools)unified_prompt_step.goinjects CLI tools prompt section (8a, after safe outputs)mcp_rendering.godocuments why CLI servers stay in gateway configmcp_renderer.goinvokesnode start_mcp_gateway.cjs(converted frombash start_mcp_gateway.sh)getMCPCLIServerNames()always includessafeoutputsandmcpscriptswhen they are enabled (HasSafeOutputsEnabled/IsMCPScriptsEnabled), regardless ofmount-as-clissettinggetMCPCLIExcludeFromAgentConfig()returns only servers that should be removed from agent MCP config (does not includesafeoutputs/mcpscripts— they get dual access)Dual access for safeoutputs and mcpscripts
safeoutputsandmcpscriptsare always CLI-mounted when enabled, even withoutmount-as-clis: truealwaysCLIMountedServersmap in Go controls which servers get this treatmentGateway scripts (bash → JavaScript)
start_mcp_gateway.cjs— full JavaScript port of the gateway startup script usingchild_process.spawnfor Docker container management, Node.jshttpmodule for health checks, andchild_process.execSyncfor converter/checker dispatch. Maintains all security controls (umask, symlink attack prevention, 0o600 file permissions)convert_gateway_config_{copilot,claude,codex,gemini}.cjs— all four engine converters are JavaScript for CLI server filtering, URL rewriting, and engine-specific config generationvalidate_prompt_placeholders.sh— strips backtick code spans before checking for unreplaced__GH_AW_*__placeholdersConfig storage hardening (
/tmp→RUNNER_TEMP)mcp-config/) and CLI manifest (mcp-cli/) moved from/tmp/gh-aw/to${RUNNER_TEMP}/gh-aw/to prevent tampering —/tmpis world-writable,RUNNER_TEMPis per-runner and not world-accessiblestart_mcp_gateway.cjs, converter scripts,mount_mcp_as_cli.cjs,redact_secrets.cjs) and Go engine code (claude_engine.go,codex_engine.go,codex_mcp.go,claude_mcp.go,gemini_mcp.go,mcp_setup_generator.go,threat_detection.go,step_order_validation.go)/tmp/gh-aw/mcp-logs/since the Docker gateway container writes there via-v /tmp:/tmp:rwSecurity hardening (shell injection, input validation)
shellEscapeDoubleQuoted()inmount_mcp_as_cli.cjs— escapes$ `` \ " !and strips\r\nfor all values embedded in generated CLI wrapper bash scripts (server name, URL, tools file path, API key), preventing shell injection and line injection attacksisValidServerName()inmount_mcp_as_cli.cjs— validates server names against[a-zA-Z0-9_-]with 64-char max length, preventing path traversal and shell metacharacter injection via malicious server names; servers failing validation are skipped with a warningnew URL()before processinggatewayPortverified to be strictly numeric before interpolation intoexecSyncshell commands instart_mcp_gateway.cjscheck_mcp_servers.shcall uses$MCP_GATEWAY_API_KEYenv var reference instead of direct JS string interpolation to avoid shell metacharacter risksstart_mcp_gateway.cjsfilters invalid server names when generating the CLI manifestObservability improvements
core.*logging viashim.cjs(core.info,core.error,core.warning) instead ofconsole.*, ensuring consistent GitHub Actions annotation outputstart_mcp_gateway.cjslogs startup banner (engine, port, domain, runner temp), server names in CLI manifest; top-level error handler preserves stack traces viacore.error(err.stack)beforecore.setFailed()mount_mcp_as_cli.cjslogs manifest server count after internal filtering, skipped servers with validation error reasonsServer 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 always CLI-mounted when enabled (dual access with MCP), with usage examples and preference guidance 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 24374939921
✨ PR Review Safe Output Test - Run 24375143641