feat: generate mcp-config.json from MCPG runtime output instead of compile time#252
feat: generate mcp-config.json from MCPG runtime output instead of compile time#252jamesadevine wants to merge 20 commits intomainfrom
Conversation
…mpile time
Replaces the compile-time generate_mcp_client_config() approach with runtime
conversion of MCPG's actual gateway output, matching gh-aw's
convert_gateway_config_copilot.cjs pattern.
Changes:
- Remove generate_mcp_client_config() and {{ mcp_client_config }} template marker
- MCPG stdout is now captured to gateway-output.json
- After health check, poll until gateway output contains valid JSON
- Use jq to transform URLs (127.0.0.1 -> host.docker.internal) and add tools: [*]
- Apply same changes to both standalone and 1ES templates
- Update AGENTS.md documentation
This ensures the Copilot CLI config reflects MCPG's actual runtime state rather
than a compile-time prediction, fixing agents not seeing configured MCPs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The stderr redirect to /tmp/gh-aw/mcp-logs/stderr.log failed because the directory didn't exist yet. Add it to the mkdir -p call alongside the gateway output directory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use process substitution to tee stderr to both the log file and the pipeline console, giving real-time visibility into MCPG startup logs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Approach is sound and solves the real problem. One silent-failure bug in the shell script worth fixing before merge. Findings🐛 Bugs / Logic Issues
|
Copy /tmp/gh-aw/mcp-logs/ into staging/logs/mcpg/ so MCPG gateway logs, stderr output, and gateway-output.json are available in the published pipeline artifact for post-run debugging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid approach — the runtime conversion correctly mirrors gh-aw's pattern, but there's one real bug around Findings🐛 Bugs / Logic Issues
|
MCPG has a 30s startup timeout for stdio MCP containers. The previous approach used npx -y @azure-devops/mcp which downloads the package at container launch time, regularly exceeding this timeout. The AzureDevOpsExtension now emits a prepare_steps() that builds a local Docker image (ado-mcp-cached:latest) with @azure-devops/mcp pre-installed via npm install -g. The MCPG config references this cached image, so the container starts instantly without any network download. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good overall — the runtime approach is clearly the right fix. A few items worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
The ADO MCP container runs via npx which needs npm registry access to resolve @azure-devops/mcp. AWF's host-level iptables rules block outbound traffic from MCPG-spawned containers unless the domains are allowlisted. Add 'node' ecosystem identifier to AzureDevOpsExtension::required_hosts() so npm/node domains are included in AWF's allowed domains list. Also reverts the cached image approach (pre-install via docker commit) as allowing network access is the correct fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good — the root cause fix is correct and the runtime conversion approach is sound. Two minor issues worth addressing; one of them is a bug (missing space in a comment isn't a bug, but one item below is a code-level correctness concern). Findings
|
Two changes to surface MCPG failures early: 1. Pass DEBUG=* to the MCPG container to enable full debug logging on stderr (streamed live via tee). This surfaces backend launch details, connection errors, and timeout diagnostics. 2. Add a 'Verify MCP backends' step after MCPG starts that probes each configured backend with a tools/list call. This forces MCPG's lazy initialization and catches failures (e.g., container timeout, network blocked) before the agent runs. Failures are surfaced as ADO pipeline warnings. Relates to #254 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Good approach — runtime generation is clearly correct over compile-time baking. A few concerns worth addressing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
The probe was using curl -sf which swallows error responses (exit 22 on 4xx). Now captures HTTP status code and response body separately so we can see what MCPG actually returns on failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Secret variables set via ##vso[task.setvariable;issecret=true] must be explicitly mapped via env: to be available in bash steps. The probe was using \ macro syntax in the script body, but bash interprets \ as command substitution before ADO can expand it. Pass the key via env: mapping as MCPG_API_KEY and reference it as \ in the script. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MCPG spec 7.1 requires the API key directly in the Authorization header, not as a Bearer token. The probe was sending 'Authorization: Bearer <key>' but MCPG expects 'Authorization: <key>'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MCP requires an initialize handshake before any other method. The probe now sends initialize first, captures the Mcp-Session-Id from the response header, then sends tools/list with that session ID. Also parses SSE data lines to extract the tool count. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid approach — the runtime conversion correctly mirrors gh-aw's pattern and fixes a real bug, but there's a silent-failure risk in the jq transform and a debug flag left permanently enabled. Findings🐛 Bugs / Logic Issues
|
AWF's DOCKER-USER iptables rules block outbound traffic from containers on Docker's default bridge network. The ADO MCP container (spawned by MCPG as a sibling container) was unable to reach dev.azure.com, causing MCP error -32001 (request timed out) on every tool call. Adding --network host via the args field bypasses the FORWARD chain rules, matching gh-aw's approach for its built-in agentic-workflows MCP server (see mcp_config_builtin.go). SafeOutputs was unaffected because it runs as an HTTP backend on localhost — no FORWARD chain involved. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move debug/diagnostic pipeline additions behind a compile-time flag
(--debug-pipeline, debug builds only) matching the --skip-integrity
pattern:
- MCPG DEBUG=* env var and stderr tee → {{ mcpg_debug_flags }} marker
- MCP backend probe step → {{ verify_mcp_backends }} marker
When --debug-pipeline is not passed (the default), both markers resolve
to empty strings and the generated pipeline contains no debug
diagnostics. MCPG log artifact collection is kept always-on as it is
low-cost and useful in production.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ADO MCP container still times out on API calls despite --network host. Adding DEBUG=* enables verbose logging from the @azure-devops/mcp npm package to surface the root cause (auth issues, DNS, proxy, etc.). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: The runtime MCP config approach is a solid improvement, but there's a bash syntax bug in the debug-pipeline flag expansion that would break MCPG startup when Findings🐛 Bugs / Logic Issues
|
Add 12 new compiler integration tests covering: - --skip-integrity: omits integrity step, produces valid YAML (both targets) - --debug-pipeline: includes DEBUG env, stderr tee, probe step, produces valid YAML (both targets) - Combined flags work together - Default (no flags) excludes debug content and includes integrity step - Probe step indentation is correct for both standalone (8sp) and 1ES (18sp) Also fix replacement content indentation: replace_with_indent adds the marker's indent to continuation lines, so replacement content must have zero base indent. Also refactor compile_fixture into compile_fixture_with_flags with atomic counter for unique temp dirs to prevent parallel test collisions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Debug replacements must run BEFORE extra_replacements so that
{{ mcpg_port }} inside the probe step content gets resolved by the
subsequent extra_replacements pass. Previously, debug replacements
ran after extra_replacements, leaving {{ mcpg_port }} unresolved.
Add test_debug_pipeline_no_unresolved_markers to catch this class of
ordering bugs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix bash line continuation in the MCPG docker run command: - mcpg_debug_flags emits backslash when disabled (maintains line continuation) and -e DEBUG env flag when enabled - Stderr tee is always-on (baked into template), not debug-gated - Fix replacement ordering: debug before extra_replacements so mcpg_port in probe step content gets resolved - Drop mcpg_stderr_redirect marker (back to 2 markers total) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the ADO-specific special case in generate_mcpg_docker_env with a generic mechanism driven by the CompilerExtension trait. New trait method: required_pipeline_vars() -> Vec<PipelineEnvMapping> - Extensions declare which container env vars map to pipeline variables - AzureDevOpsExtension maps AZURE_DEVOPS_EXT_PAT -> SC_READ_TOKEN The compiler generates the full chain generically: 1. env: block on MCPG step (ADO secret -> bash var) 2. -e flag on MCPG docker run (bash var -> MCPG process env) 3. MCPG config keeps empty string (MCPG passthrough to child) ADO secrets require env: mapping because Azure DevOps does not expand secret variables via macro syntax in script bodies. Also updates azure-devops-mcp-agent fixture to use first-class tools.azure-devops instead of manual mcp-servers configuration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…KEN) The @azure-devops/mcp npm package does NOT use AZURE_DEVOPS_EXT_PAT (that's for the az devops CLI extension). It accepts auth type via CLI arg (-a) and reads the token from a specific env var per type. For pipeline use with bearer tokens from az account get-access-token: - Add '-a envvar' to entrypoint args (selects envvar auth mode) - Map ADO_MCP_AUTH_TOKEN (not AZURE_DEVOPS_EXT_PAT) to SC_READ_TOKEN - The ADO MCP reads the bearer token from ADO_MCP_AUTH_TOKEN Auth types in @azure-devops/mcp (src/auth.ts): - 'pat': reads PERSONAL_ACCESS_TOKEN (base64 PAT) - 'envvar': reads ADO_MCP_AUTH_TOKEN (bearer token) ← we use this - 'azcli'/'env': uses DefaultAzureCredential - 'interactive': OAuth browser flow (default, breaks stdio) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…/runtime-mcp-config
🔍 Rust PR ReviewSummary: Solid architecture with two real bugs and one regression worth fixing before merge. Findings🐛 Bugs / Logic Issues
|
Summary
Replaces compile-time
mcp-config.jsongeneration with runtime conversion of MCPG's actual gateway output, matching gh-aw'sconvert_gateway_config_copilot.cjspattern.Problem
The agent inside AWF could not see configured MCP servers. The
mcp-config.json(Copilot CLI client config) was generated at compile time and baked into the pipeline YAML via a{{ mcp_client_config }}heredoc. This produced configs that didn't match MCPG's actual runtime state — the agent had no working MCP connections.Solution
Mirror gh-aw's approach precisely:
gateway-output.json(stderr → log file)mcpServers(health check alone doesn't guarantee stdout is flushed)127.0.0.1→host.docker.internal) and settools: ["*"]/tmp/awf-tools/mcp-config.jsonfor the AWF-sandboxed agentWhy the URL rewrite?
MCPG runs with
--network hostand binds to127.0.0.1. But the agent runs inside an AWF Docker container on a separate network —127.0.0.1there is the container's own loopback.host.docker.internalis the only path back to the host from inside AWF.Changes
src/compile/common.rsgenerate_mcp_client_config()function and 3 associated testssrc/compile/standalone.rsmcp_client_configimport, generation, and template replacementsrc/compile/onees.rssrc/data/base.yml{{ mcp_client_config }}heredoc; MCPG step now captures stdout, polls for output, transforms with jqsrc/data/1es-base.ymlAGENTS.md{{ mcp_client_config }}docs to reflect the new runtime approachNet: -89 lines (115 added, 204 removed)
Testing
cargo build✅cargo test— all 66 tests pass ✅examples/sample-agent.md— output contains no{{ mcp_client_config }}, has jq-based conversion ✅