Shell Scripting Mcp
Build automation pipelines, CI/CD workflows, and monitoring scripts that talk to MCP servers.
Foundations
Every mcp2cli command supports --json output with a consistent envelope. This makes it composable with standard Unix tools.
The JSON Envelope
{ "app_id": "work", "command": "invoke", "summary": "called echo", "lines": ["..."], "data": { /* command-specific structured data */ }}Exit Codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Runtime error (server error, timeout, etc.) |
2 | CLI usage error (bad flags, missing args) |
Common Patterns
Extract Data with jq
# Tool nameswork --json ls --tools | jq -r '.data.items[].id'
# Tool result contentwork --json echo --message hello | jq -r '.data.content[0].text'
# Server healthwork --json doctor | jq '.data.server'
# Capability countTOOLS=$(work --json ls --tools | jq '.data.items | length')echo "Server has $TOOLS tools"Conditional Logic
# Check if server is upif work --timeout 5 ping >/dev/null 2>&1; then echo "Server is up"else echo "Server is down" exit 1fi
# Check if a specific tool existsif work --json ls --tools | jq -e '.data.items[] | select(.id == "deploy")' >/dev/null 2>&1; then echo "Deploy tool available"fiLooping Over Tools
# Call every tool with a smoke testwork --json ls --tools | jq -r '.data.items[].id' | while read tool; do echo -n "Testing $tool... " if work --json --timeout 10 "$tool" >/dev/null 2>&1; then echo "OK" else echo "FAIL" fidonePiping Between Commands
# Get resource list, then read each onework --json ls --resources | jq -r '.data.items[].id' | while read uri; do work --json get "$uri" > "resources/$(echo $uri | tr '/:' '__').json"doneCI/CD Recipes
Deployment Pipeline
#!/bin/bashset -euo pipefail
VERSION="${1:?Usage: $0 <version>}"CONFIG="prod"
echo "=== Deploying v$VERSION ==="
# Pre-flight checksecho "Running health check...""$CONFIG" --timeout 10 doctor || { echo "Server unhealthy"; exit 1; }
# Deployecho "Submitting deployment..."RESULT=$("$CONFIG" --json deploy --version "$VERSION" --background)JOB_ID=$(echo "$RESULT" | jq -r '.data.job_id')echo "Job: $JOB_ID"
# Wait with timeoutecho "Waiting for completion..."if timeout 600 "$CONFIG" --json jobs wait "$JOB_ID"; then echo "✅ Deploy v$VERSION succeeded"else echo "❌ Deploy timed out" "$CONFIG" jobs cancel "$JOB_ID" exit 1fi
# Post-deploy verificationecho "Verifying...""$CONFIG" --timeout 5 pingecho "✅ Server responding after deploy"Nightly Regression Test
#!/bin/bash# Run nightly in cron: 0 2 * * * /scripts/nightly-mcp-test.sh
LOG="/var/log/mcp-nightly/$(date +%Y%m%d).log"PASS=0FAIL=0
test_tool() { local tool="$1" shift if work --json --timeout 30 "$tool" "$@" >> "$LOG" 2>&1; then ((PASS++)) else echo "FAIL: $tool" >> "$LOG" ((FAIL++)) fi}
echo "=== Nightly Test $(date) ===" >> "$LOG"
# Discoverytest_tool lstest_tool ls --toolstest_tool ls --resources
# Core toolstest_tool echo --message "nightly-test-$(date +%s)"test_tool add --a 7 --b 13test_tool ping
# Reportecho "Results: $PASS passed, $FAIL failed" >> "$LOG"
if [ "$FAIL" -gt 0 ]; then # Send alert (Slack, email, etc.) curl -s -X POST "$SLACK_WEBHOOK" \ -d "{\"text\": \"🔴 MCP Nightly: $FAIL tests failed. See $LOG\"}"fiDocker Health Check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD mcp2cli --url http://localhost:3001/mcp --timeout 5 pingEnvironment Variable Patterns
Per-Environment Configs
# Developmentexport MCP2CLI_CONFIG_DIR=./configs/devwork deploy --version 1.0
# Stagingexport MCP2CLI_CONFIG_DIR=./configs/stagingwork deploy --version 1.0
# Productionexport MCP2CLI_CONFIG_DIR=./configs/prodwork deploy --version 1.0Dynamic Endpoint Override
# Point config at a different server without changing config filesMCP2CLI_SERVER__ENDPOINT=https://canary.api/mcp work --json doctorSecret Injection
# Inject secrets from a vaultexport API_KEY=$(vault kv get -field=api_key secret/mcp-server)mcp2cli --stdio "my-server" --env "API_KEY=$API_KEY" echo --message testMonitoring Integration
Event-Driven Monitoring
# Config for monitored serverevents: http_endpoint: "http://prometheus-pushgateway:9091/metrics/job/mcp" command: | echo "mcp_event_total{type=\"${MCP_EVENT_TYPE}\",app=\"${MCP_EVENT_APP_ID}\"} 1" | \ curl -s --data-binary @- http://prometheus-pushgateway:9091/metrics/job/mcpProbe Script for Uptime Monitoring
#!/bin/bash# Prometheus-compatible probeSTART=$(date +%s%N)if mcp2cli --url "$MCP_ENDPOINT" --timeout 10 --json ping >/dev/null 2>&1; then END=$(date +%s%N) LATENCY=$(( (END - START) / 1000000 )) echo "mcp_probe_success 1" echo "mcp_probe_latency_ms $LATENCY"else echo "mcp_probe_success 0"fiNDJSON Streaming
For real-time pipelines, use NDJSON output:
# Stream events to a log aggregatorwork --output ndjson ls 2>/dev/null | \ while IFS= read -r line; do echo "$line" | curl -s -X POST http://log-aggregator/ingest -d @- doneError Handling Patterns
# Retry with backoffretry() { local max_attempts=3 local delay=2 local attempt=1
while [ $attempt -le $max_attempts ]; do if "$@"; then return 0 fi echo "Attempt $attempt failed, retrying in ${delay}s..." sleep $delay delay=$((delay * 2)) attempt=$((attempt + 1)) done
echo "All $max_attempts attempts failed" return 1}
# Usageretry work --timeout 30 deploy --version 2.0Capture and Parse Errors
OUTPUT=$(work --json echo --message hello 2>&1)EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then echo "Command failed with exit code $EXIT_CODE" echo "Error: $OUTPUT"else echo "$OUTPUT" | jq '.data.content'fiParallel Execution
# Run multiple commands in parallelwork --json --timeout 30 tool-a --arg x &work --json --timeout 30 tool-b --arg y &work --json --timeout 30 tool-c --arg z &waitecho "All commands completed"For many parallel commands, use GNU parallel:
work --json ls --tools | jq -r '.data.items[].id' | \ parallel -j4 "work --json --timeout 30 {} 2>/dev/null > /tmp/results/{}.json"See Also
- Output Formats — JSON envelope details
- Request Timeouts — timeout control
- Event System — event sinks for monitoring
- Testing MCP Servers — testing-specific patterns
- AI Agents + MCP via CLI — agent integration