Skip to main content

CI/CD Platform Automation

Create and update collections, proxies, and workflow configuration from a CI/CD pipeline using the Management MCP. Every change made via the pipeline uses the same OIDC identity and role-based access as the admin UI — one audit trail for both human and automated changes.

Typical scenario: Your team promotes API configuration from a development environment to production as part of a release pipeline. A GitHub Actions job creates a new collection and proxy in production, validates the configuration, then exits with a success or failure signal.


How it works​

The Management MCP exposes platform management operations as MCP tools over Streamable HTTP. Your pipeline authenticates with an OIDC token from your identity provider, initializes a session, and calls tools to read or write platform configuration.

Supported operations include:

  • list_collections, get_collection, create_collection, update_collection, toggle_collection_status
  • list_proxies, get_proxy, create_proxy, update_proxy
  • get_proxy_workflow, update_proxy_workflow, validate

Prerequisites​

  • Your identity provider (e.g. Okta, Auth0, Azure AD) is configured as an OIDC issuer for the platform
  • A service account or machine identity is registered with the modifier role in the platform
  • The Management MCP endpoint is accessible from your CI runner (e.g. https://api.example.com/api/v1/mcp)

Step 1 — Obtain an OIDC token in the pipeline​

In GitHub Actions, use the built-in OIDC token exchange to get a token from your IdP:

# .github/workflows/deploy-api-config.yml
name: Deploy API Configuration

on:
push:
branches: [main]
paths:
- 'api-config/**'

permissions:
id-token: write
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Get OIDC token
id: oidc
run: |
TOKEN=$(curl -s -X POST \
"${{ vars.OIDC_TOKEN_URL }}" \
-d "grant_type=client_credentials" \
-d "client_id=${{ secrets.MCP_CLIENT_ID }}" \
-d "client_secret=${{ secrets.MCP_CLIENT_SECRET }}" \
-d "scope=openid" \
| jq -r '.access_token')
echo "token=$TOKEN" >> $GITHUB_OUTPUT

- name: Deploy API config
env:
MCP_TOKEN: ${{ steps.oidc.outputs.token }}
MCP_URL: ${{ vars.MCP_MANAGEMENT_URL }}
run: ./scripts/deploy-api-config.sh

Store MCP_CLIENT_ID and MCP_CLIENT_SECRET as GitHub Actions secrets. Never hard-code credentials in workflow files.


Step 2 — Initialize an MCP session​

Every Management MCP interaction starts with an initialize call. Store the returned Mcp-Session-Id for subsequent requests.

#!/bin/bash
# scripts/deploy-api-config.sh

set -euo pipefail

MCP_URL="${MCP_URL:-https://api.example.com/api/v1/mcp}"

# Initialize session
INIT_RESPONSE=$(curl -s -i https://"${MCP_URL}" \
-H "Authorization: Bearer $MCP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "init-1",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "github-actions", "version": "1.0" }
}
}')

SESSION_ID=$(echo "$INIT_RESPONSE" | grep -i "Mcp-Session-Id" | awk '{print $2}' | tr -d '\r')
echo "Session ID: $SESSION_ID"

Step 3 — Read existing state (plan phase)​

Before making any changes, read the current state to understand what already exists:

# List existing collections
list_collections() {
curl -s "$MCP_URL" \
-H "Authorization: Bearer $MCP_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"read-1","method":"tools/call","params":{"name":"list_collections","arguments":{}}}' \
| jq '.result.content[0].text | fromjson'
}

EXISTING=$(list_collections)
echo "Existing collections: $(echo $EXISTING | jq length)"

Step 4 — Create or update a collection​

# Create a new collection (idempotent — check if exists first)
create_or_update_collection() {
local NAME="$1"
local BASE_PATH="$2"

curl -s "$MCP_URL" \
-H "Authorization: Bearer $MCP_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\": \"2.0\",
\"id\": \"create-col-1\",
\"method\": \"tools/call\",
\"params\": {
\"name\": \"create_collection\",
\"arguments\": {
\"name\": \"$NAME\",
\"basePath\": \"$BASE_PATH\",
\"description\": \"Deployed by CI pipeline on $(date -u +%Y-%m-%dT%H:%M:%SZ)\"
}
}
}" | jq '.result'
}

COLLECTION=$(create_or_update_collection "payments-api-v2" "/payments/v2")
COLLECTION_ID=$(echo $COLLECTION | jq -r '.content[0].text | fromjson | .id')
echo "Collection ID: $COLLECTION_ID"

Step 5 — Add a proxy to the collection​

# Create a proxy within the collection
create_proxy() {
local COLLECTION_ID="$1"

curl -s "$MCP_URL" \
-H "Authorization: Bearer $MCP_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\": \"2.0\",
\"id\": \"create-proxy-1\",
\"method\": \"tools/call\",
\"params\": {
\"name\": \"create_proxy\",
\"arguments\": {
\"collectionId\": \"$COLLECTION_ID\",
\"name\": \"Process Payment\",
\"path\": \"/process\",
\"method\": \"POST\",
\"targetUrl\": \"https://payments.internal/v2/process\",
\"description\": \"Payment processing endpoint\"
}
}
}" | jq '.result'
}

create_proxy "$COLLECTION_ID"

Step 6 — Validate and publish​

# Validate the collection configuration
validate_collection() {
local COLLECTION_ID="$1"

curl -s "$MCP_URL" \
-H "Authorization: Bearer $MCP_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\": \"2.0\",
\"id\": \"validate-1\",
\"method\": \"tools/call\",
\"params\": {
\"name\": \"validate\",
\"arguments\": { \"collectionId\": \"$COLLECTION_ID\" }
}
}" | jq '.result.content[0].text | fromjson'
}

VALIDATION=$(validate_collection "$COLLECTION_ID")
STATUS=$(echo $VALIDATION | jq -r '.status')

if [ "$STATUS" != "valid" ]; then
echo "Validation failed: $VALIDATION"
exit 1
fi

# Toggle collection to active (published)
curl -s "$MCP_URL" \
-H "Authorization: Bearer $MCP_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\": \"2.0\",
\"id\": \"publish-1\",
\"method\": \"tools/call\",
\"params\": {
\"name\": \"toggle_collection_status\",
\"arguments\": { \"collectionId\": \"$COLLECTION_ID\", \"active\": true }
}
}" | jq '.result'

echo "Collection published successfully."

Step 7 — Smoke test after deploy​

After publishing, send a test request through the gateway to confirm the proxy is live:

HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
https://api.example.com/payments/v2/process \
-X POST \
-H "Authorization: Bearer $SMOKE_TEST_TOKEN" \
-H "X-Client-ID: $SMOKE_TEST_CLIENT_ID" \
-H "X-Profile-ID: $SMOKE_TEST_PROFILE_ID" \
-H "Content-Type: application/json" \
-d '{"amount": 1, "currency": "USD"}')

if [ "$HTTP_STATUS" != "200" ] && [ "$HTTP_STATUS" != "201" ]; then
echo "Smoke test failed with status $HTTP_STATUS"
exit 1
fi

echo "Smoke test passed: $HTTP_STATUS"

CI/CD safety gates​

Follow these practices for safe automated changes:

GateHow to implement
Read before writeAlways call list_collections before create_collection
Validate before publishRun validate before toggle_collection_status
Smoke test after publishTest through gateway with a smoke test client
Separate environmentsUse different OIDC service accounts per environment (dev/staging/prod)
Fail fastExit non-zero on any unexpected error; do not continue a partial deploy

Audit trail​

Every MCP tool call is recorded in the platform audit log. Open Logs → Audit Logs and filter by the service account identity to see the full history of pipeline-created changes alongside UI-created changes — one unified audit trail.


Next steps​