Conversation
This change modifies the gh-aw compiler to use gh-aw-mcpg (MCP gateway) instead of the deprecated awmg binary. gh-aw-mcpg runs as a Docker container on the Actions runner host, and AWF containers connect to it via host.docker.internal. Key changes: - Add gateway constants and types for gh-aw-mcpg - Add gateway.go with Docker command generation and helper functions - Add MCP field to SandboxConfig for gateway configuration - Add --enable-host-access AWF flag when MCP gateway is enabled - Update health check script for gh-aw-mcpg - Add host.docker.internal to CodexDefaultDomains - Remove awmg references from Makefile 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
|
🎉 Yo ho ho! Smoke Copilot Safe Inputs found the treasure and completed successfully! ⚓💰 |
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟 |
|
🎉 Yo ho ho! Changeset Generator found the treasure and completed successfully! ⚓💰 |
|
📰 VERDICT: Smoke Copilot Playwright has concluded. All systems operational. This is a developing story. 🎤 |
|
🤖 DIAGNOSTIC COMPLETE: Smoke Copilot No Firewall STATUS: ALL_UNITS_OPERATIONAL. MISSION_SUCCESS. |
|
✅ Firewall validation complete... Smoke Codex Firewall confirmed network sandboxing is operational. 🛡️ |
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
Smoke Test Results✅ File Writing: Created test file successfully Status: PASS (2/3 core tests passed, gh auth failure expected in this context)
|
Smoke Test Results (Run 20763192290)❌ Playwright MCP - Not available Overall Status: FAIL (Note: Playwright MCP server unavailable via CLI in this workflow execution)
|
✅ Smoke Test Results - Copilot Engine (No Firewall)All tests passed successfully:
|
|
PRs: #9163 Bump gh-aw-firewall (AWF) to v0.8.2; #9162 Update Codex CLI to version 0.78.0
|
Smoke Test Results (Copilot Engine)Last 2 Merged PRs:
Tests:
Status: PASS cc @Mossaka (PR author + assignee)
|
|
Smoke test (Codex):
|
There was a problem hiding this comment.
Pull request overview
This PR replaces the removed awmg MCP gateway with the new gh-aw-mcpg Docker container implementation. The changes establish the infrastructure for running MCP gateway as a Docker container that AWF containers can access via host.docker.internal, enabling secure MCP server communication in firewall mode.
Key Changes:
- Introduces
gateway.gowith gateway management functions and Docker command generation - Adds gateway constants (default port 80, image, version v0.1.0, session token)
- Implements MCP gateway configuration extraction in frontmatter parsing
- Adds
--enable-host-accessAWF flag across all engines when MCP gateway is enabled
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/gateway.go | New file implementing MCP gateway Docker container management, configuration transformation, and health check logic |
| pkg/workflow/mcp_gateway_constants.go | Updated constants for gh-aw-mcpg (port 80, Docker image, version v0.1.0, container names) |
| pkg/workflow/tools_types.go | Updated MCPGatewayRuntimeConfig documentation to reflect gh-aw-mcpg and host.docker.internal architecture |
| pkg/workflow/sandbox.go | Added MCP field to SandboxConfig for gateway configuration |
| pkg/workflow/frontmatter_extraction_security.go | Implemented extractMCPGatewayConfig to parse sandbox.mcp configuration from YAML |
| pkg/workflow/copilot_engine_execution.go | Added --enable-host-access flag when MCP gateway is enabled |
| pkg/workflow/codex_engine.go | Added --enable-host-access flag when MCP gateway is enabled |
| pkg/workflow/claude_engine.go | Added --enable-host-access flag when MCP gateway is enabled |
| pkg/workflow/domains.go | Added host.docker.internal to CodexDefaultDomains for gateway access |
| pkg/workflow/mcp_renderer.go | Updated comments from awmg to gh-aw-mcpg |
| actions/setup/sh/verify_mcp_gateway_health.sh | Updated health check script for gh-aw-mcpg container with Docker container checks and improved diagnostics |
| Makefile | Removed awmg binary references from clean target |
Comments suppressed due to low confidence (1)
pkg/workflow/frontmatter_extraction_security.go:303
- The version validation in extractMCPGatewayConfig only checks for "latest" but doesn't validate that the version starts with 'v' or handle empty versions, unlike ValidateGatewayVersion in gateway.go which enforces these rules. This inconsistency means invalid versions (like "0.1.0" without 'v' prefix or empty strings) could pass through extraction but fail later during command generation.
Consider using ValidateGatewayVersion here to ensure consistent validation at extraction time, providing earlier and clearer error messages to users. Alternatively, document why extraction has looser validation than generation.
// Extract version (MUST be pinned, not "latest")
if version, ok := mcpObj["version"].(string); ok {
if version == "latest" {
frontmatterExtractionSecurityLog.Print("Warning: MCP gateway version 'latest' is not allowed, using default pinned version")
config.Version = DefaultMCPGatewayVersion
} else {
config.Version = version
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // extractMCPGatewayConfig extracts MCP gateway configuration from sandbox.mcp | ||
| func (c *Compiler) extractMCPGatewayConfig(mcpVal any) *MCPGatewayRuntimeConfig { | ||
| mcpObj, ok := mcpVal.(map[string]any) | ||
| if !ok { | ||
| // If mcp is just "true" or some non-object, use defaults | ||
| return &MCPGatewayRuntimeConfig{} | ||
| } | ||
|
|
||
| config := &MCPGatewayRuntimeConfig{} | ||
|
|
||
| // Extract version (MUST be pinned, not "latest") | ||
| if version, ok := mcpObj["version"].(string); ok { | ||
| if version == "latest" { | ||
| frontmatterExtractionSecurityLog.Print("Warning: MCP gateway version 'latest' is not allowed, using default pinned version") | ||
| config.Version = DefaultMCPGatewayVersion | ||
| } else { | ||
| config.Version = version | ||
| } | ||
| } | ||
|
|
||
| // Extract port | ||
| if port, ok := mcpObj["port"].(int); ok { | ||
| config.Port = port | ||
| } else if portFloat, ok := mcpObj["port"].(float64); ok { | ||
| config.Port = int(portFloat) | ||
| } | ||
|
|
||
| // Extract session-token | ||
| if sessionToken, ok := mcpObj["session-token"].(string); ok { | ||
| config.SessionToken = sessionToken | ||
| } | ||
|
|
||
| // Extract api-key (for backward compatibility) | ||
| if apiKey, ok := mcpObj["api-key"].(string); ok { | ||
| config.APIKey = apiKey | ||
| } | ||
|
|
||
| // Extract domain | ||
| if domain, ok := mcpObj["domain"].(string); ok { | ||
| config.Domain = domain | ||
| } | ||
|
|
||
| // Extract custom container image | ||
| if container, ok := mcpObj["container"].(string); ok { | ||
| config.Container = container | ||
| } | ||
|
|
||
| // Extract command (for custom gateway binary) | ||
| if command, ok := mcpObj["command"].(string); ok { | ||
| config.Command = command | ||
| } | ||
|
|
||
| // Extract args | ||
| if args, ok := mcpObj["args"].([]any); ok { | ||
| for _, arg := range args { | ||
| if argStr, ok := arg.(string); ok { | ||
| config.Args = append(config.Args, argStr) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Extract env | ||
| if env, ok := mcpObj["env"].(map[string]any); ok { | ||
| config.Env = make(map[string]string) | ||
| for key, val := range env { | ||
| if valStr, ok := val.(string); ok { | ||
| config.Env[key] = valStr | ||
| } | ||
| } | ||
| } | ||
|
|
||
| frontmatterExtractionSecurityLog.Printf("Extracted MCP gateway config: version=%s, port=%d", config.Version, config.Port) | ||
| return config | ||
| } |
There was a problem hiding this comment.
The extractMCPGatewayConfig function lacks test coverage. This function performs critical configuration extraction from YAML including validation logic (e.g., rejecting "latest" version) and type conversions that should be thoroughly tested.
Consider adding tests in frontmatter_extraction_security_test.go for:
- Valid MCP gateway configurations
- Invalid configurations (e.g., version="latest")
- Type conversions for port field (int and float64)
- Edge cases like nil values, empty objects
- All field extractions (version, port, session-token, api-key, domain, container, command, args, env)
This issue also appears in the following locations of the same file:
- line 295
| dockerCmd := fmt.Sprintf(`cat %s | docker run \ | ||
| --rm -i \ | ||
| --name %s \ | ||
| -v /var/run/docker.sock:/var/run/docker.sock \ | ||
| -p %d:%d \ | ||
| --add-host host.docker.internal:host-gateway \ | ||
| -e GITHUB_PERSONAL_ACCESS_TOKEN \ | ||
| %s \ | ||
| --routed --listen 0.0.0.0:%d --config-stdin \ | ||
| > %s/gateway.log 2>&1 &`, | ||
| mcpConfigPath, | ||
| MCPGatewayContainerName, | ||
| port, | ||
| MCPGatewayContainerPort, | ||
| image, | ||
| MCPGatewayContainerPort, | ||
| MCPGatewayLogsFolder, | ||
| ) |
There was a problem hiding this comment.
The Docker command has a potential shell injection vulnerability through mcpConfigPath. While the path is hardcoded in GenerateMCPGatewayStartStep (line 134), the function GenerateMCPGatewayDockerCommands accepts it as a parameter without validation. If a caller passes user-controlled input, it could be exploited.
Consider adding input validation for mcpConfigPath or using a fixed constant path within this function. Alternatively, ensure all callers are documented to never pass user-controlled values, and add a security comment to this effect.
| // Check if safe-inputs is enabled to include host.docker.internal in allowed domains | ||
| hasSafeInputs := IsSafeInputsEnabled(workflowData.SafeInputs, workflowData) | ||
|
|
||
| // Check if MCP gateway is enabled | ||
| hasMCPGateway := IsMCPGatewayEnabled(workflowData) | ||
|
|
||
| // Get allowed domains (copilot defaults + network permissions + host.docker.internal if safe-inputs enabled) | ||
| allowedDomains := GetCopilotAllowedDomainsWithSafeInputs(workflowData.NetworkPermissions, hasSafeInputs) | ||
|
|
||
| // Build AWF arguments: mount points + standard flags + custom args from config | ||
| var awfArgs []string | ||
| awfArgs = append(awfArgs, "--env-all") | ||
|
|
||
| // Add --enable-host-access when MCP gateway is enabled | ||
| // This allows AWF containers to reach gh-aw-mcpg running on the host | ||
| if hasMCPGateway { | ||
| awfArgs = append(awfArgs, "--enable-host-access") | ||
| copilotExecLog.Print("Added --enable-host-access for MCP gateway access") | ||
| } |
There was a problem hiding this comment.
There's an inconsistency in how MCP gateway access is enabled for different engines. The Copilot and Claude engines check hasMCPGateway to add --enable-host-access (lines 228-243 in copilot_engine_execution.go and 238-253 in claude_engine.go), but the allowed domains calculation still relies only on hasSafeInputs parameter via GetCopilotAllowedDomainsWithSafeInputs/GetClaudeAllowedDomainsWithSafeInputs.
While host.docker.internal is now in the default domains, the logic should be consistent: if we're checking hasMCPGateway for --enable-host-access, we should also consider it when calculating domains or document why the MCP gateway check is separate from domain configuration.
|
|
||
| # Check if gh-aw-mcpg container is running | ||
| echo '=== Docker Container Check ===' | ||
| if docker ps | grep -q gh-aw-mcpg; then |
There was a problem hiding this comment.
The grep command on line 52 has a potential issue: it will match any container whose name contains "gh-aw-mcpg" anywhere in the output, not just the container name field. For example, it could match a container with an image name or other field containing that string.
Consider using docker ps with --filter "name=gh-aw-mcpg" directly in the condition instead of piping to grep, which would be more precise and avoid potential false positives. For example:
if docker ps --filter "name=gh-aw-mcpg" --format "{{.Names}}" | grep -q "^gh-aw-mcpg$"; thenThis ensures an exact match on the container name.
| if docker ps | grep -q gh-aw-mcpg; then | |
| if docker ps --filter "name=gh-aw-mcpg" --format "{{.Names}}" | grep -q "^gh-aw-mcpg$"; then |
| package workflow | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/githubnext/gh-aw/pkg/logger" | ||
| ) | ||
|
|
||
| var gatewayLog = logger.New("workflow:gateway") | ||
|
|
||
| // ValidateGatewayVersion validates that a gateway version is properly pinned | ||
| // Returns an error if the version is "latest" or empty | ||
| func ValidateGatewayVersion(version string) error { | ||
| if version == "latest" { | ||
| return fmt.Errorf("gh-aw-mcpg version must be pinned (e.g., v0.1.0), 'latest' is not allowed for reproducibility") | ||
| } | ||
| if version == "" { | ||
| return fmt.Errorf("gh-aw-mcpg version must be specified") | ||
| } | ||
| if !strings.HasPrefix(version, "v") { | ||
| return fmt.Errorf("gh-aw-mcpg version must start with 'v' (e.g., v0.1.0), got: %s", version) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // GetGatewayVersion returns the gateway version to use, defaulting to DefaultMCPGatewayVersion | ||
| func GetGatewayVersion(config *MCPGatewayRuntimeConfig) string { | ||
| if config != nil && config.Version != "" { | ||
| return config.Version | ||
| } | ||
| return DefaultMCPGatewayVersion | ||
| } | ||
|
|
||
| // GetGatewayPort returns the gateway port to use, defaulting to DefaultMCPGatewayPort | ||
| func GetGatewayPort(config *MCPGatewayRuntimeConfig) int { | ||
| if config != nil && config.Port > 0 { | ||
| return config.Port | ||
| } | ||
| return DefaultMCPGatewayPort | ||
| } | ||
|
|
||
| // GetGatewaySessionToken returns the session token to use, defaulting to DefaultGatewaySessionToken | ||
| func GetGatewaySessionToken(config *MCPGatewayRuntimeConfig) string { | ||
| if config != nil && config.SessionToken != "" { | ||
| return config.SessionToken | ||
| } | ||
| return DefaultGatewaySessionToken | ||
| } | ||
|
|
||
| // GenerateMCPGatewayDockerCommands generates the commands to start gh-aw-mcpg as a Docker container | ||
| // The gateway runs on the host and AWF containers connect to it via host.docker.internal | ||
| func GenerateMCPGatewayDockerCommands(config *MCPGatewayRuntimeConfig, mcpConfigPath string) []string { | ||
| version := GetGatewayVersion(config) | ||
| port := GetGatewayPort(config) | ||
|
|
||
| // Validate version at generation time | ||
| if err := ValidateGatewayVersion(version); err != nil { | ||
| gatewayLog.Printf("Warning: %v, using default version %s", err, DefaultMCPGatewayVersion) | ||
| version = DefaultMCPGatewayVersion | ||
| } | ||
|
|
||
| image := fmt.Sprintf("%s:%s", DefaultMCPGatewayImage, version) | ||
|
|
||
| gatewayLog.Printf("Generating gh-aw-mcpg Docker commands: image=%s, port=%d", image, port) | ||
|
|
||
| var commands []string | ||
|
|
||
| // Create logs directory | ||
| commands = append(commands, fmt.Sprintf("mkdir -p %s", MCPGatewayLogsFolder)) | ||
|
|
||
| // Build the Docker run command | ||
| // The config is piped via stdin to avoid file system issues | ||
| dockerCmd := fmt.Sprintf(`cat %s | docker run \ | ||
| --rm -i \ | ||
| --name %s \ | ||
| -v /var/run/docker.sock:/var/run/docker.sock \ | ||
| -p %d:%d \ | ||
| --add-host host.docker.internal:host-gateway \ | ||
| -e GITHUB_PERSONAL_ACCESS_TOKEN \ | ||
| %s \ | ||
| --routed --listen 0.0.0.0:%d --config-stdin \ | ||
| > %s/gateway.log 2>&1 &`, | ||
| mcpConfigPath, | ||
| MCPGatewayContainerName, | ||
| port, | ||
| MCPGatewayContainerPort, | ||
| image, | ||
| MCPGatewayContainerPort, | ||
| MCPGatewayLogsFolder, | ||
| ) | ||
|
|
||
| commands = append(commands, dockerCmd) | ||
|
|
||
| // Wait for gateway to be healthy | ||
| commands = append(commands, fmt.Sprintf(`echo "Waiting for gh-aw-mcpg to be ready..." | ||
| for i in $(seq 1 30); do | ||
| if curl -sf http://localhost:%d/health > /dev/null 2>&1; then | ||
| echo "gh-aw-mcpg is ready" | ||
| break | ||
| fi | ||
| if [ $i -eq 30 ]; then | ||
| echo "ERROR: gh-aw-mcpg failed to start" | ||
| cat %s/gateway.log | ||
| exit 1 | ||
| fi | ||
| sleep 1 | ||
| done`, port, MCPGatewayLogsFolder)) | ||
|
|
||
| return commands | ||
| } | ||
|
|
||
| // GenerateMCPGatewayStartStep generates the GitHub Actions step to start the MCP gateway | ||
| // This step starts gh-aw-mcpg as a Docker container | ||
| func GenerateMCPGatewayStartStep(config *MCPGatewayRuntimeConfig, mcpEnvVars map[string]string) GitHubActionStep { | ||
| gatewayLog.Print("Generating MCP gateway start step") | ||
|
|
||
| var stepLines []string | ||
|
|
||
| stepLines = append(stepLines, " - name: Start MCP Gateway") | ||
| stepLines = append(stepLines, " id: mcp-gateway-start") | ||
|
|
||
| // Add environment variables | ||
| if len(mcpEnvVars) > 0 { | ||
| stepLines = append(stepLines, " env:") | ||
| for key, value := range mcpEnvVars { | ||
| stepLines = append(stepLines, fmt.Sprintf(" %s: %s", key, value)) | ||
| } | ||
| } | ||
|
|
||
| stepLines = append(stepLines, " run: |") | ||
|
|
||
| // Generate the Docker commands | ||
| mcpConfigPath := "/tmp/gh-aw/mcpg-config.json" | ||
| commands := GenerateMCPGatewayDockerCommands(config, mcpConfigPath) | ||
| for _, cmd := range commands { | ||
| // Indent each line of multi-line commands | ||
| for _, line := range strings.Split(cmd, "\n") { | ||
| stepLines = append(stepLines, fmt.Sprintf(" %s", line)) | ||
| } | ||
| } | ||
|
|
||
| return GitHubActionStep(stepLines) | ||
| } | ||
|
|
||
| // TransformMCPConfigForGatewayClient transforms MCP server configs for the agent client | ||
| // Each server is converted to use HTTP transport via the gateway | ||
| func TransformMCPConfigForGatewayClient(mcpServers map[string]any, config *MCPGatewayRuntimeConfig) map[string]any { | ||
| sessionToken := GetGatewaySessionToken(config) | ||
| port := GetGatewayPort(config) | ||
|
|
||
| transformed := make(map[string]any) | ||
| for serverName := range mcpServers { | ||
| transformed[serverName] = map[string]any{ | ||
| "type": "http", | ||
| "url": fmt.Sprintf("http://host.docker.internal:%d/mcp/%s", port, serverName), | ||
| "headers": map[string]any{ | ||
| "Authorization": fmt.Sprintf("Bearer %s", sessionToken), | ||
| }, | ||
| "tools": []string{"*"}, | ||
| } | ||
| } | ||
|
|
||
| gatewayLog.Printf("Transformed %d MCP servers for gateway client access", len(transformed)) | ||
| return transformed | ||
| } | ||
|
|
||
| // IsMCPGatewayEnabled checks if MCP gateway is enabled in the workflow configuration | ||
| func IsMCPGatewayEnabled(workflowData *WorkflowData) bool { | ||
| if workflowData == nil || workflowData.SandboxConfig == nil { | ||
| return false | ||
| } | ||
| // Gateway is enabled when sandbox.mcp is configured | ||
| return workflowData.SandboxConfig.MCP != nil | ||
| } | ||
|
|
||
| // GetMCPGatewayConfig returns the MCP gateway configuration from workflow data | ||
| func GetMCPGatewayConfig(workflowData *WorkflowData) *MCPGatewayRuntimeConfig { | ||
| if workflowData == nil || workflowData.SandboxConfig == nil { | ||
| return nil | ||
| } | ||
| return workflowData.SandboxConfig.MCP | ||
| } | ||
|
|
||
| // GenerateGatewayConfigPath returns the path to the gateway config file | ||
| func GenerateGatewayConfigPath() string { | ||
| return "/tmp/gh-aw/mcpg-config.json" | ||
| } | ||
|
|
||
| // GenerateClientMCPConfigForGateway generates the MCP client config that routes | ||
| // all MCP server requests through the gateway | ||
| func GenerateClientMCPConfigForGateway(yaml *strings.Builder, serverNames []string, config *MCPGatewayRuntimeConfig, format string) { | ||
| sessionToken := GetGatewaySessionToken(config) | ||
| port := GetGatewayPort(config) | ||
|
|
||
| gatewayLog.Printf("Generating client MCP config for %d servers via gateway", len(serverNames)) | ||
|
|
||
| if format == "json" { | ||
| // JSON format for Copilot/Claude | ||
| for i, serverName := range serverNames { | ||
| isLast := i == len(serverNames)-1 | ||
| yaml.WriteString(fmt.Sprintf(" \"%s\": {\n", serverName)) | ||
| yaml.WriteString(" \"type\": \"http\",\n") | ||
| yaml.WriteString(fmt.Sprintf(" \"url\": \"http://host.docker.internal:%d/mcp/%s\",\n", port, serverName)) | ||
| yaml.WriteString(" \"headers\": {\n") | ||
| yaml.WriteString(fmt.Sprintf(" \"Authorization\": \"Bearer %s\"\n", sessionToken)) | ||
| yaml.WriteString(" },\n") | ||
| yaml.WriteString(" \"tools\": [\"*\"]\n") | ||
| if isLast { | ||
| yaml.WriteString(" }\n") | ||
| } else { | ||
| yaml.WriteString(" },\n") | ||
| } | ||
| } | ||
| } else if format == "toml" { | ||
| // TOML format for Codex | ||
| for _, serverName := range serverNames { | ||
| yaml.WriteString("\n") | ||
| yaml.WriteString(fmt.Sprintf(" [mcp_servers.%s]\n", serverName)) | ||
| yaml.WriteString(" type = \"http\"\n") | ||
| yaml.WriteString(fmt.Sprintf(" url = \"http://host.docker.internal:%d/mcp/%s\"\n", port, serverName)) | ||
| yaml.WriteString(fmt.Sprintf(" headers = { Authorization = \"Bearer %s\" }\n", sessionToken)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // ShouldUseGatewayForMCP determines if the MCP gateway should be used | ||
| // Returns true if: | ||
| // - sandbox.mcp is configured | ||
| // - AWF (firewall) is enabled | ||
| func ShouldUseGatewayForMCP(workflowData *WorkflowData) bool { | ||
| if !IsMCPGatewayEnabled(workflowData) { | ||
| return false | ||
| } | ||
| // Gateway only makes sense when firewall is enabled | ||
| // because the gateway runs on the host and containers connect to it | ||
| return isFirewallEnabled(workflowData) | ||
| } |
There was a problem hiding this comment.
The new gateway.go file lacks test coverage. This is a significant addition with 238 lines of code including critical functions like ValidateGatewayVersion, GenerateMCPGatewayDockerCommands, and TransformMCPConfigForGatewayClient that should have comprehensive unit tests to ensure correctness and prevent regressions.
Consider adding gateway_test.go with tests for:
- ValidateGatewayVersion with valid/invalid versions
- GetGatewayVersion/Port/SessionToken with various config combinations
- GenerateMCPGatewayDockerCommands output validation
- TransformMCPConfigForGatewayClient transformation logic
- IsMCPGatewayEnabled and GetMCPGatewayConfig edge cases
Summary
Test plan
🤖 Generated with Claude Code
Changeset
awmgwithgh-aw-mcpgMCP gateway container; add gateway constants, types, and configuration extraction; add--enable-host-accessAWF flag; update health check script.