# MCP server lifecycle

End-to-end workflow for putting an MCP server behind AIronClaw.

## 1. Pre-flight check

Before creating, list what the user already has so you don't add a duplicate:

```bash
curl -fsS -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  "${AIRONCLAW_BASE_URL}/api/mcp" | jq '.servers[] | {id, name, url}'
```

Also confirm the user has quota:

```bash
curl -fsS -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  "${AIRONCLAW_BASE_URL}/api/profile" | jq '.tags'
```

A `private:permissions:mcp:limit:N` tag tells you the cap; absence means the default (10).

## 2. Create the MCP record

The minimum body is `name` + `url`. The URL must:

- be `https://` (SSRF check rejects `http://` to private IPs and reserved hostnames)
- resolve to a public IP when DNS is queried server-side

If the upstream MCP requires its own bearer token (AIronClaw-to-MCP), pass `authType: "bearer"` and the token in `authToken` — it is encrypted at rest with AES-256-GCM and never returned in any GET.

```bash
curl -fsS -X POST \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  -H "Content-Type: application/json" \
  "${AIRONCLAW_BASE_URL}/api/mcp" \
  -d '{
    "name": "stripe-mcp",
    "url": "https://mcp.stripe.example.com",
    "authType": "bearer",
    "authToken": "sk_live_..."
  }' | jq
```

The response includes `proxyHost` (e.g. `5f2a...c4e9.aifirewall.aironclaw.com`) — this is the URL clients use to reach the MCP through AIronClaw.

## 3. Choose how clients authenticate INBOUND

Two modes (independent of upstream auth):

### 3a. AIronClaw API key (default)

Clients send `Authorization: Bearer <client-api-key>` where the key is one minted via `POST /api/keys` carrying a `mcp:<id>:tool:*` (or `mcp:<id>:tool:<name>`) permission tag. Nothing extra to do at MCP create time.

### 3b. JWT verified against a JWKS

Use this when the user's organisation already has an IdP (Auth0, Keycloak, ...) and wants its tokens to flow straight into the MCP.

Step 1 — fetch the JWKS yourself (the user's IdP, not AIronClaw):

```bash
curl -fsS https://<idp>/.well-known/jwks.json > /tmp/jwks.json
```

Step 2 — validate the document before saving:

```bash
JWKS_JSON=$(jq -Rs . < /tmp/jwks.json)   # JSON-escape to a string
curl -fsS -X POST \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  -H "Content-Type: application/json" \
  "${AIRONCLAW_BASE_URL}/api/mcp/jwks/test" \
  -d "{\"jwksJson\": ${JWKS_JSON}}" | jq
```

Step 3 — pass it in the create or update body:

```json
{
  "name": "...",
  "url": "...",
  "auth": {
    "mode": "jwt",
    "issuer": "https://idp.example.com",
    "audience": "stripe-mcp",
    "jwksJson": "<the same JSON string>",
    "clockSkewS": 30
  }
}
```

Why is the JWKS pasted in instead of fetched at runtime? AIronClaw deliberately does not fetch user-controlled URLs at request time — it would be a DNS-rebinding vector. The JWKS is provided once and re-validated on each save.

## 4. Discover the upstream tools

After create, the `tools` field is empty. Trigger discovery:

```bash
curl -fsS -X POST \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  "${AIRONCLAW_BASE_URL}/api/mcp/${MCP_ID}/tools" | jq '.tools[] | .name'
```

This connects to the upstream MCP via `tools/list` and persists the catalog. Re-run whenever the upstream changes its tools — there is no automatic poll.

## 5. Mint a scoped client API key

Now that the tools are known, mint a key for the agent / app that will call the MCP:

```bash
curl -fsS -X POST \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  -H "Content-Type: application/json" \
  "${AIRONCLAW_BASE_URL}/api/keys" \
  -d "{
    \"name\": \"stripe-mcp-prod\",
    \"mcpPermissions\": [
      { \"id\": \"${MCP_ID}\", \"tools\": [\"create_customer\", \"create_invoice\"] }
    ]
  }" | jq -r '.key.key'
```

The plaintext key is shown once. Pass it to the client as `Authorization: Bearer <key>` against `https://${MCP_PROXY_HOST}/`.

To grant the key access to **all** tools on this MCP, use `"tools": ["*"]`. To grant access to MCP resources too, add `"resources": ["*"]` or specific resource ids.

## 6. Add firewall rules (optional)

The MCP is now reachable but has no firewall rules. Common patterns:

- IP allow/deny: see [rules-and-dlp.md § ip_acl](rules-and-dlp.md#ip_acl)
- Per-tool rate limit: § rate_limit
- DLP redaction in responses: § response_replace
- Inject extra context into a tool description (e.g. usage warnings): § tool_description_inject
- Cache idempotent tool responses to cut upstream load: § static_cache
- Expose synthetic resources: § mcp_resource

Rules are a single PUT — fetch, mutate, push:

```bash
RULES=$(curl -fsS -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  "${AIRONCLAW_BASE_URL}/api/mcp/${MCP_ID}/rules" | jq '.rules')

NEW_RULE='{ "rule_type": "rate_limit", "tools": ["*"], "name": "global", "match_key": "api_key", "threshold": 100, "timespan": 60 }'

UPDATED=$(echo "$RULES" | jq ". + [$NEW_RULE]")

curl -fsS -X PUT \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  -H "Content-Type: application/json" \
  "${AIRONCLAW_BASE_URL}/api/mcp/${MCP_ID}/rules" \
  -d "{\"rules\": $UPDATED}" | jq
```

## 7. Day-2 operations

### Update the upstream URL

```bash
curl -fsS -X PATCH \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  -H "Content-Type: application/json" \
  "${AIRONCLAW_BASE_URL}/api/mcp/${MCP_ID}" \
  -d '{"url": "https://mcp-v2.stripe.example.com"}'
```

The gateway upstream/target is reconciled and the DNS pin refreshed.

### Force DNS re-resolve

If the upstream IP rotated and you don't want to wait 60s for the periodic re-pin:

```bash
curl -fsS -X POST \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  "${AIRONCLAW_BASE_URL}/api/mcp/${MCP_ID}/re-resolve" | jq
```

### Drain a tool's response cache

```bash
curl -fsS -X DELETE \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  "${AIRONCLAW_BASE_URL}/api/mcp/${MCP_ID}/cache?tool=search"
```

### Delete the MCP

Confirm with the user — this is destructive: it tears down the gateway service, both routes, the plugin, and strips every API key of any `mcp:<id>:tool:*` / `mcp:<id>:resource:*` tag.

```bash
curl -fsS -X DELETE \
  -H "Authorization: Bearer ${AIRONCLAW_TOKEN}" \
  "${AIRONCLAW_BASE_URL}/api/mcp/${MCP_ID}"
```
