This guide explains how to run an agentic pipeline locally for development and
testing using the optional ado-aw mcp-http command, which remains supported
for direct/local use. This is not what compiled pipelines run. Since the
containerized SafeOutputs architecture landed, compiled pipelines spawn
SafeOutputs as a hardened stdio child container through MCPG (ado-aw mcp,
--network none, non-root, read-only rootfs) — there is no host-side HTTP
server, bridge-gateway resolution, or host.docker.internal mapping in CI.
See docs/mcpg.md for that shape. The manual mcp-http workflow
below is a simpler, still-supported way to exercise an agent's prompt + MCP
wiring by hand without a full pipeline replica.
- ado-aw built from source (
cargo build) - Copilot CLI on your PATH (
copilot --version) - Docker (optional, required for MCPG / custom MCP servers)
- An Azure DevOps PAT if your agent uses ADO APIs
A pipeline execution has three stages:
- SafeOutputs MCP server — receives tool calls from the agent and writes
them as NDJSON records. Locally this guide uses
ado-aw mcp-http; compiled pipelines useado-aw mcpas a stdio child spawned by MCPG instead. - Agent execution — Copilot CLI runs with a prompt and MCP config, interacting with SafeOutputs (and optionally other MCPs via MCPG)
- Safe output execution — processes the NDJSON records and makes real ADO API calls (create PRs, work items, etc.)
export WORK_DIR=$(mktemp -d)
echo "Working directory: $WORK_DIR"This step is specific to the local manual workflow. Resolve the Docker bridge gateway address so a locally-run MCPG container (started outside AWF) can reach the host-side server:
# Resolve the Docker bridge gateway (same address MCPG uses to reach the host)
export SAFE_OUTPUTS_BIND_ADDRESS=$(docker network inspect bridge | jq -er '.[0].IPAM.Config[0].Gateway')
# Pick a port and generate an API key
export SO_PORT=8100
export SO_API_KEY=$(openssl rand -hex 32)
# Start in the background, bound only to the bridge gateway address
cargo run -- mcp-http \
--bind-address "$SAFE_OUTPUTS_BIND_ADDRESS" \
--port "$SO_PORT" \
--api-key "$SO_API_KEY" \
"$WORK_DIR" \
"$(pwd)" \
> "$WORK_DIR/safeoutputs.log" 2>&1 &
export SO_PID=$!
echo "SafeOutputs PID: $SO_PID"
# Wait for health check
until curl -sf "http://$SAFE_OUTPUTS_BIND_ADDRESS:$SO_PORT/health" > /dev/null 2>&1; do
sleep 1
done
echo "SafeOutputs ready"Skip this step if your agent only uses SafeOutputs (no mcp-servers: or
tools: azure-devops: in front matter).
export MCPG_PORT=8080
export MCPG_API_KEY=$(openssl rand -hex 32)
# Generate MCPG config for this manual local flow. Note: compiled pipelines
# wire "safeoutputs" as a hardened stdio child container (`type: "stdio"`,
# container: the pinned AWF `agent` image, entrypoint: ado-aw mcp ...; see
# docs/mcpg.md), not the HTTP backend shown below -- the HTTP shape here is
# specific to this local/manual workflow, which runs SafeOutputs as a plain
# host process for simplicity.
cat > "$WORK_DIR/mcpg-config.json" <<EOF
{
"mcpServers": {
"safeoutputs": {
"type": "http",
"url": "http://host.docker.internal:$SO_PORT/mcp",
"headers": {
"Authorization": "Bearer $SO_API_KEY"
}
}
},
"gateway": {
"port": $MCPG_PORT,
"domain": "127.0.0.1",
"apiKey": "$MCPG_API_KEY"
}
}
EOF
# Start MCPG on Docker's default bridge network, published to localhost.
# The bridge network + docker-socket + port-mapping shape below matches what
# compiled pipelines do for the MCPG container itself; only the SafeOutputs
# backend type differs (HTTP here vs. stdio child container in CI).
# The local Copilot process runs on the host, so the config above advertises
# 127.0.0.1; compiled AWF pipelines advertise awmg-mcpg instead.
docker run -i --rm --name awmg-mcpg \
--network bridge \
-p "127.0.0.1:$MCPG_PORT:$MCPG_PORT" \
--add-host "host.docker.internal:$SAFE_OUTPUTS_BIND_ADDRESS" \
-v /var/run/docker.sock:/var/run/docker.sock \
--entrypoint /app/awmg \
ghcr.io/github/gh-aw-mcpg:v0.4.1 \
--routed --listen "0.0.0.0:$MCPG_PORT" --config-stdin \
< "$WORK_DIR/mcpg-config.json" \
> "$WORK_DIR/gateway-output.json" 2>"$WORK_DIR/mcpg-stderr.log" &
export MCPG_PID=$!
# Wait for MCPG health check
until curl -sf "http://127.0.0.1:$MCPG_PORT/health" > /dev/null 2>&1; do
sleep 1
done
echo "MCPG ready"Without MCPG (SafeOutputs only):
cat > "$WORK_DIR/mcp-config.json" <<EOF
{
"mcpServers": {
"safeoutputs": {
"type": "http",
"url": "http://$SAFE_OUTPUTS_BIND_ADDRESS:$SO_PORT/mcp",
"headers": {
"Authorization": "Bearer $SO_API_KEY"
},
"tools": ["*"]
}
}
}
EOFWith MCPG — transform the gateway output:
# Wait for gateway-output.json to contain valid JSON, then rewrite for copilot
python3 -c "
import json, sys
with open('$WORK_DIR/gateway-output.json') as f:
config = json.load(f)
for name, server in config.get('mcpServers', {}).items():
server['tools'] = ['*']
with open('$WORK_DIR/mcp-config.json', 'w') as f:
json.dump(config, f, indent=2)
"Extract the markdown body (everything after the YAML front matter) from your agent file:
AGENT_FILE=path/to/your-agent.md
# Extract body after front matter (after second ---)
awk '/^---$/{n++; next} n>=2' "$AGENT_FILE" > "$WORK_DIR/agent-prompt.md"copilot \
--prompt "@$WORK_DIR/agent-prompt.md" \
--additional-mcp-config "@$WORK_DIR/mcp-config.json" \
--model claude-opus-4.7 \
--no-ask-user \
--disable-builtin-mcps \
--allow-all-toolsAdjust flags based on your agent's front matter (model, allowed tools, etc.).
cargo run -- execute \
--source "$AGENT_FILE" \
--safe-output-dir "$WORK_DIR" \
--dry-run # Remove --dry-run to make real ADO API calls# Stop SafeOutputs
kill "$SO_PID" 2>/dev/null
# Stop MCPG (if started)
docker stop awmg-mcpg 2>/dev/null
echo "Done. Output files in: $WORK_DIR"- Use
--dry-runon the execute step to validate safe outputs without making real ADO API calls - Set
AZURE_DEVOPS_EXT_PATfor agents that need ADO API access - Check
$WORK_DIR/safeoutputs.logand$WORK_DIR/mcpg-stderr.logfor debugging - The compiled pipeline YAML shows the exact flags and config used in CI — use
ado-aw compile your-agent.mdand inspect the output for reference