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