feat: mount MCP servers as local CLIs after gateway starts#24503
feat: mount MCP servers as local CLIs after gateway starts#24503
Conversation
- Add `start_mcp_gateway.sh`: save CLI manifest (server names + local URLs) to /tmp/gh-aw/mcp-cli/manifest.json before cleanup - Add `mount_mcp_as_cli.sh`: new shell script that reads the manifest, queries each server's tools/list via JSON-RPC, and generates a standalone CLI wrapper script in /tmp/gh-aw/mcp-cli/bin/ (chmod 555) - Add `mcp_cli_mount.go`: Go helpers getMCPCLIServerNames() and generateMCPCLIMountStep() + buildMCPCLIPromptSection() - Add "Mount MCP servers as CLIs" workflow step in compiler_yaml_main_job.go - Inject MCP CLI tools prompt section in unified_prompt_step.go - Update golden test fixtures Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f83b0114-9c57-4701-ac7d-bfa80f07a971 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
| <safe-output-tools> | ||
| Tools: add_comment(max:10), create_issue(max:5), create_discussion(max:2), missing_tool, missing_data, noop | ||
| </safe-output-tools> | ||
| ## MCP Tools Available as CLI Commands |
actions/setup/sh/mount_mcp_as_cli.sh
Outdated
| @@ -0,0 +1,309 @@ | |||
| #!/usr/bin/env bash | |||
There was a problem hiding this comment.
- convert this a hell script to actions/github-script (and delete after conversion)
- add logging
There was a problem hiding this comment.
Done in f3d7205. Converted to actions/setup/js/mount_mcp_as_cli.cjs with core.info() logging throughout (and core.warning() for non-fatal errors). The shell script has been deleted. The workflow step now uses actions/github-script.
There was a problem hiding this comment.
Great work converting to github-script! The added core.info() logging makes it much easier to debug MCP mounting issues in the Actions log. 🎉
📰 BREAKING: Report filed by Smoke Copilot · ● 860.4K
that enables this behavior. Default is false.
|
…ml tags - Delete mount_mcp_as_cli.sh shell script - Add mount_mcp_as_cli.cjs using actions/github-script with core.info() logging - Update generateMCPCLIMountStep to use actions/github-script - Wrap MCP CLI prompt section in <mcp-clis> XML tags - Update golden fixtures and recompile lock files Agent-Logs-Url: https://github.com/github/gh-aw/sessions/1bf60ced-fb56-48d7-a2ce-b870a185cc27 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. 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 add a field tools.mount-as-clis: true/false that enables this behavior. Default is false. When false, do not emit custom prompt or mount any MCPs. |
|
✅ 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 shadows whisper... Smoke Codex failed. The oracle requires further meditation... |
|
📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing... |
|
@copilot the cli wrappers must implement a proper mcp client protocol and maintain a session |
This comment has been minimized.
This comment has been minimized.
|
Smoke Test #23981224949 |
Overall:
|
There was a problem hiding this comment.
This PR adds MCP server CLI mounting to all workflow lock files — a useful improvement for discoverability. The pattern of continue-on-error: true is appropriate for non-critical tooling. Two minor suggestions: add explicit success logging in the script, and consider documenting the mount_mcp_as_cli.cjs function signature for future maintainers.
📰 BREAKING: Report filed by Smoke Copilot · ● 860.4K
| "apiKey": "${MCP_GATEWAY_API_KEY}", | ||
| "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" | ||
| } | ||
| } |
There was a problem hiding this comment.
Consider adding a debug output after await main() (e.g., core.info('MCP CLI mounts completed')) to make it easier to diagnose issues in the Actions log when continue-on-error silently eats failures.
| "apiKey": "${MCP_GATEWAY_API_KEY}", | ||
| "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" | ||
| } | ||
| } |
There was a problem hiding this comment.
The continue-on-error: true flag is appropriate here since mounting MCP CLIs is non-critical — failing silently is better than blocking the workflow. Good defensive pattern. 👍
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
This comment has been minimized.
This comment has been minimized.
Done in 3a7b43c. Merged main and recompiled all 181 workflows successfully. 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 END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
|
🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation... |
|
📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing... |
Agent Container Tool Check
Result: 12/12 tools available ✅ Overall Status: PASS
|
|
Smoke Test Run 23987300602 — Core: ✅✅✅✅✅❌✅✅✅✅✅✅ | PR Review: ✅✅✅
|
There was a problem hiding this comment.
💥 Automated smoke test review - all systems nominal!
💥 [THE END] — Illustrated by Smoke Claude · ● 218.8K
| let data = ""; | ||
| res.on("data", chunk => { | ||
| data += chunk; | ||
| }); |
There was a problem hiding this comment.
The httpPostJSON function uses a timeout via req.setTimeout but calls req.destroy() which may not cleanly abort ongoing requests. Consider using req.abort() or wrapping in an AbortController for more predictable cleanup.
| const path = require("path"); | ||
|
|
||
| const MANIFEST_FILE = "/tmp/gh-aw/mcp-cli/manifest.json"; | ||
| // Use RUNNER_TEMP so the bin and tools directories are inside the AWF sandbox mount |
There was a problem hiding this comment.
Consider adding a comment explaining that RUNNER_TEMP is intentionally used over /tmp/gh-aw/ here — the reason (AWF sandbox mount) is important for maintainability and is otherwise non-obvious.
|
Smoke Test Results — Run §23987300615
Overall: PR Author:
|
There was a problem hiding this comment.
Smoke test review for PR #24503 — MCP CLI mounting feature. Two confirmed bugs found during smoke test validation:
-
Generated bash scripts have syntax error (
actions/setup/js/mount_mcp_as_cli.cjs:335): The nested\\"escaping produces\""in the output bash, which terminates the double-quoted string prematurely. Confirmed: running the generated CLI scripts fails withbash: line 177: unexpected EOF while looking for matching '"'. -
Server name mismatch (
pkg/workflow/mcp_cli_mount.go:54):"agentic-workflows"vs"agenticworkflows"(no hyphen) — filtering in the gateway config won't work correctly.
Otherwise the feature concept is solid — baking the API key into the script, locking the bin dir with chmod 555, and the MCP session handshake logic are all well done.
📰 BREAKING: Report filed by Smoke Copilot · ● 2.3M
| if echo "\$response" | jq -e ".result.content" >/dev/null 2>&1; then | ||
| echo "\$response" | jq -r ".result.content[] | | ||
| if .type == \\"text\\" then .text | ||
| elif .type == \\"image\\" then \\"[image data - \(.mimeType // \\"unknown\\")]\\"" |
There was a problem hiding this comment.
Bug: Generated bash script has an unclosed quote causing syntax error.
The \\" escaping at end of this line generates \"" in the bash script. In bash double-quoted context, \" is an escaped quote and the trailing " terminates the string prematurely — leaving else (. | tostring)\nend" outside the string, causing bash -n to fail with "unexpected EOF while looking for matching "'".
This is confirmed by testing the generated scripts in this smoke test run.
Fix: Use single quotes for the jq expression, or restructure to avoid nested double-quote escaping:
echo "\$response" | jq -r '.result.content[] | if .type == "text" then .text elif .type == "image" then "[image data - \\(.mimeType // \\"unknown\\")]" else (. | tostring) end'
pkg/workflow/mcp_cli_mount.go
Outdated
| } | ||
| // Only include tools that have MCP servers (skip bash, web-fetch, web-search, edit, cache-memory, etc.) | ||
| switch toolName { | ||
| case "github", "playwright", "qmd", "agentic-workflows": |
There was a problem hiding this comment.
The tool name "agentic-workflows" here doesn't match the MCP server ID used in the gateway config (agenticworkflows without the hyphen). This means GH_AW_MCP_CLI_SERVERS will contain "agentic-workflows" but the gateway config filtering looks for "agenticworkflows", so the server won't be removed from agent MCP config and the generated CLI name won't match the actual server. Consider using constants.AgenticWorkflowsMCPServerID.String() to stay in sync.
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
|
@copilot Confirmed Bug: Generated bash wrapper scripts (from |
…smatch - Fix bash 'unexpected EOF' in generated CLI wrapper: switch the multi-line jq -r expression from double-quoted to single-quoted bash string so that double quotes inside the jq filter don't prematurely close the outer string (mount_mcp_as_cli.cjs:335) - Fix agentic-workflows server ID mismatch: getMCPCLIServerNames now emits 'agenticworkflows' (gateway/manifest ID) instead of 'agentic-workflows' (frontmatter key), so GH_AW_MCP_CLI_SERVERS correctly matches manifest entries and the convert_gateway_config_*.sh filter works (mcp_cli_mount.go:54) Agent-Logs-Url: https://github.com/github/gh-aw/sessions/faa03f79-19e9-4c74-b505-9a9b11fdd786 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Fixed in 7526d82:
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:
|
Summary
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: trueflag (defaultfalse). When enabled, all user-facing MCP servers — including built-in ones likegithubandplaywrightas well as custom servers — are exposed as standalone shell commands and excluded from the agent's final MCP config so the agent uses the CLI wrappers exclusively. Whenfalse, no CLI mounting step is emitted and no prompt section is injected.How It Works
After the MCP gateway starts, a new "Mount MCP servers as CLIs" workflow step runs
mount_mcp_as_cli.cjsviaactions/github-script. This script:start_mcp_gateway.shbefore cleanup)initialize→notifications/initialized→tools/list) to discover available tools${RUNNER_TEMP}/gh-aw/mcp-cli/bin/<server-name>chmod 555) so the agent cannot modify or add scriptsPATHviacore.addPath()for subsequent stepsThe agent is also informed about these tools via an injected prompt section (wrapped in
<mcp-clis>XML tags) listing the available CLI commands.Architecture: Gateway vs. Agent Config
The MCP gateway must start Docker containers for all configured servers — including CLI-mounted ones — so they have reachable endpoints for the CLI manifest. The filtering is therefore applied in two stages:
start_mcp_gateway.sh): includes all servers so the gateway starts their containers and populates/tmp/gh-aw/mcp-cli/manifest.json.convert_gateway_config_*.sh): CLI-mounted servers are excluded via theGH_AW_MCP_CLI_SERVERSenvironment variable (a JSON array exported in the gateway step), so the agent cannot reach them directly and must use the CLI wrappers.AWF Sandbox Path
CLI wrapper scripts and tools JSON are generated under
${RUNNER_TEMP}/gh-aw/mcp-cli/(not/tmp/gh-aw/) so they are accessible inside the AWF sandbox, which mounts${RUNNER_TEMP}/gh-awread-only. The manifest file written bystart_mcp_gateway.shremains at/tmp/gh-aw/mcp-cli/manifest.jsonsince it is only read during the setup step, which runs on the host before AWF starts.Changes
New:
actions/setup/js/mount_mcp_as_cli.cjs/tmp/gh-aw/mcp-cli/manifest.jsoninitialize— establishes session, capturesMcp-Session-Idnotifications/initialized— required handshake notification (server responds 204 No Content)tools/list/tools/call— executed within the established session<server> --help— list all available tools<server> <tool> --help— show tool description and parameters<server> <tool> [--param value ...]— call a tool (follows the same 3-step MCP session protocol per invocation)safeoutputs,mcp-scripts) andcache-memory(file share, not an MCP server) are excluded${RUNNER_TEMP}/gh-aw/mcp-cli/bin/in achmod 555directory the agent cannot write to${RUNNER_TEMP}/gh-aw/mcp-cli/tools/so scripts can access it inside AWFcore.info()/core.warning()logging throughoutNew:
actions/setup/md/mcp_cli_tools_prompt.md<mcp-clis>prompt section, following the same pattern as other prompt templates (safe_outputs_prompt.md,github_mcp_tools_prompt.md, etc.)__GH_AW_MCP_CLI_SERVERS_LIST__placeholder substituted at runtime by thesubstitute_placeholdersstepcat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"in compiled lock filesModified:
actions/setup/sh/start_mcp_gateway.sh/tmp/gh-aw/mcp-cli/manifest.jsonwith server names and local URLs beforegateway-output.jsonis deletedGH_AW_MCP_CLI_SERVERSModified:
actions/setup/sh/convert_gateway_config_*.sh(Copilot, Claude, Codex, Gemini)GH_AW_MCP_CLI_SERVERS(JSON array) and excludes those servers from the agent's final MCP config using--argjson cliServers "${GH_AW_MCP_CLI_SERVERS:-[]}"in thejqcallNew:
pkg/workflow/mcp_cli_mount.gogetMCPCLIServerNames()— returns sorted list of user-facing MCP servers to mount (built-in and custom); returns nil whentools.mount-as-clisis nottruegenerateMCPCLIMountStep()— generates the "Mount MCP servers as CLIs"actions/github-scriptstep in compiled workflows; no-op when flag is falsebuildMCPCLIPromptSection()— returnsIsFile: truereferencingmcp_cli_tools_prompt.mdwith the server list passed asGH_AW_MCP_CLI_SERVERS_LISTenv var; no-op when flag is falseModified:
pkg/workflow/tools_types.goandpkg/workflow/tools_parser.goMountAsCLIs boolfield toToolsConfig(parsed fromtools.mount-as-clis, defaultfalse)Modified:
pkg/parser/schemas/main_workflow_schema.jsonmount-as-clisboolean property to the tools schemaModified:
pkg/workflow/mcp_rendering.gocomposeCLIMountFilter— CLI-mounted servers are no longer excluded from the gateway config (they must be started by the gateway). Filtering is now delegated to the conversion scripts.Modified:
pkg/workflow/mcp_setup_generator.goGH_AW_MCP_CLI_SERVERS(JSON array built withjson.Marshal) in the gateway step script, afterGH_AW_ENGINE, so conversion scripts can exclude those servers from the agent configModified:
pkg/workflow/compiler_yaml_main_job.gogenerateMCPCLIMountStep()aftergenerateMCPSetup()completesModified:
pkg/workflow/unified_prompt_step.go<mcp-clis>XML tags) after safe outputs (section 8a)Modified:
actions/setup/sh/validate_prompt_placeholders.sh__GH_AW_*__placeholders, so documented placeholder names in PR descriptions or code examples are not flagged as errorsModified:
.github/workflows/smoke-copilot.mdmount-as-clis: trueto exercise the feature in smoke testinggithub pull_request_read --method list ...,serena activate_project --path ...,playwright browser_navigate --url ...)Updated
.lock.ymlfiles recompiledExample
For a workflow configured with
mount-as-clis: true,playwright,github, and a customtavilyMCP server, the agent will have:Those servers are excluded from the agent's MCP config, so the agent interacts with them exclusively through the CLI wrappers. The gateway still starts all Docker containers so the CLI wrappers can communicate with them.
The prompt injected to the agent (loaded from
mcp_cli_tools_prompt.md):✨ PR Review Safe Output Test - Run 23985224528
✨ PR Review Safe Output Test - Run 23987300602