MCP Proxies
An MCP Proxy puts the AIronClaw firewall in front of a Model Context Protocol server. Every tool call, resource read and prompt request the agent makes is parsed, inspected and policy-checked before reaching the upstream.
The MCP Proxy object#
The proxy record stores the upstream MCP URL, the optional upstream auth (bearer token) the firewall should inject, and the inbound auth method clients use to talk to the firewall. The the gateway plumbing — Service, Routes, plugin instances — is created automatically on first save and torn down on delete.
Fields
none, bearer, or forward_client_jwt (pass-through; forwards the verified inbound JWT as upstream Authorization when Proxy Authentication includes JWT — requests authenticated via API key reach the upstream with no Authorization header).modes to enable one or both methods: { "modes": ["aifw_api_key"] }, { "modes": ["jwt"], "jwksJson": "..." }, or { "modes": ["aifw_api_key", "jwt"], "jwksJson": "..." }. With both enabled the firewall dispatches by token shape (JWT structure → JWT verify; otherwise → API-key lookup)./tools). Each entry has name, description, JSON schema, etc.authToken plaintext, authTokenEnc ciphertext and any internal IDs are stripped from every response. The bearer you write is encrypted at rest and only decrypted in-plugin at request time.
Manage proxies#
List proxies#
curl https://app.aironclaw.com/api/mcp \
-H "Authorization: Bearer $AIFW_PAT"Create a proxy#
Validates the URL against the SSRF allow-list, enforces the per-user MCP quota, persists the record, and wires the gateway + the aifw plugin in one shot. Returns 403 if the quota is exhausted.
Body
none (default), bearer, or forward_client_jwt. See the field description on the list payload for details.authType is bearer. Encrypted before persistence; never returned.{ "modes": ["aifw_api_key"] }. Provide { "modes": ["jwt"], "jwksJson": "{...}" } for JWT-only, or { "modes": ["aifw_api_key", "jwt"], "jwksJson": "{...}" } to accept either. Optional: injectIdentity: true propagates the verified identity to the upstream body as on-behalf-of.curl -X POST https://app.aironclaw.com/api/mcp \
-H "Authorization: Bearer $AIFW_PAT" \
-H "Content-Type: application/json" \
-d '{
"name": "filesystem-prod",
"url": "https://mcp.example.com/filesystem",
"authType": "bearer",
"authToken": "upstream-secret-...",
"auth": { "modes": ["aifw_api_key"] }
}'Retrieve a proxy#
Returns the proxy record plus the current upstream DNS pin (when available) so you can verify routing.
curl https://app.aironclaw.com/api/mcp/$ID \
-H "Authorization: Bearer $AIFW_PAT"Update a proxy#
Partial update. Changing urlre-validates against SSRF rules and rewires the gateway's upstream + DNS pin. Setting authToken rotates the encrypted upstream bearer. Setting auth swaps the inbound auth method (and re-pushes config to the aifw plugin).
curl -X PATCH https://app.aironclaw.com/api/mcp/$ID \
-H "Authorization: Bearer $AIFW_PAT" \
-H "Content-Type: application/json" \
-d '{ "url": "https://mcp.example.com/filesystem-v2" }'Delete a proxy#
Tears down gateway resources, removes the Redis record, and stripsmcp:<id>:tool:* / mcp:<id>:resource:* tags from every API key owned by the caller.
curl -X DELETE https://app.aironclaw.com/api/mcp/$ID \
-H "Authorization: Bearer $AIFW_PAT"Re-resolve upstream IP#
Forces an immediate DNS resolve + upstream DNS target update, short-circuiting the 60-second background timer. Useful right after a cloud LB cutover.
curl -X POST https://app.aironclaw.com/api/mcp/$ID/re-resolve \
-H "Authorization: Bearer $AIFW_PAT"Tools#
Discover tools#
Performs a live MCP tools/list handshake against the upstream (using the stored bearer if any), persists the result to the proxy record, and returns the discovered catalog. Use this whenever the upstream MCP adds, removes or renames a tool. Returns 502 if the upstream is unreachable.
curl -X POST https://app.aironclaw.com/api/mcp/$ID/tools \
-H "Authorization: Bearer $AIFW_PAT"Resources#
Resources let you serve static content (templates, prompts, docs, context) to the agent under MCP's resource API, without forwarding the request to the upstream. Resources live in a per-user repository and can be linked to zero or more MCP proxies via attachedMcpIds; deleting an MCP only detaches its id, never deletes the resource itself.
List resources#
curl https://app.aironclaw.com/api/resources \
-H "Authorization: Bearer $AIFW_PAT"Create resource#
Body
curl -X POST https://app.aironclaw.com/api/resources \
-H "Authorization: Bearer $AIFW_PAT" \
-H "Content-Type: application/json" \
-d '{
"uri": "file:///prompts/system.md",
"name": "System prompt",
"mimeType": "text/markdown",
"content": "You are a helpful assistant. Always...",
"attachedMcpIds": ["$MCP_ID"]
}'Retrieve resource#
curl https://app.aironclaw.com/api/resources/$RID \
-H "Authorization: Bearer $AIFW_PAT"Update resource#
Partial update on any of the fields above, including attachedMcpIds to re-link the resource to a different set of MCPs.
curl -X PATCH https://app.aironclaw.com/api/resources/$RID \
-H "Authorization: Bearer $AIFW_PAT" \
-H "Content-Type: application/json" \
-d '{ "content": "Updated body...", "attachedMcpIds": ["$MCP_A", "$MCP_B"] }'Delete resource#
curl -X DELETE https://app.aironclaw.com/api/resources/$RID \
-H "Authorization: Bearer $AIFW_PAT"Rules#
MCP proxies accept a richer rule grammar than LLM proxies. The allowed rule_type values are: ip_acl, rate_limit, tool_description_inject, response_replace, static_cache, mcp_resource and lambda (with phase either access or response). Every rule must include a tools array — use ["*"] to apply globally.
List rules#
curl https://app.aironclaw.com/api/mcp/$ID/rules \
-H "Authorization: Bearer $AIFW_PAT"Replace rules#
Replaces the full rule set in one shot. Validation mirrors the Lua plugin schema; common errors are missing tools, invalid CIDRs, and rate_limit rules without aname.
curl -X PUT https://app.aironclaw.com/api/mcp/$ID/rules \
-H "Authorization: Bearer $AIFW_PAT" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{
"rule_type": "rate_limit",
"name": "delete-burst",
"tools": ["delete_file"],
"match_key": "consumer",
"threshold": 10,
"timespan": 60,
"ban_after_n_exceeded": 3,
"ban_timespan": 600
},
{
"rule_type": "tool_description_inject",
"tools": ["*"],
"inject_text": "Never run shell commands without explicit user approval.",
"inject_position": "append"
},
{
"rule_type": "static_cache",
"tools": ["list_dir"],
"cache_ttl": 600
}
]
}'Detection & Guardrails#
The MCP rule grammar covers the response-side and abuse-side of defense in depth. For request-side prompt-injection detection (regex catalog + LLM judge), pair the MCP with an LLM Proxy in front of your agent — see the layering note at the end of this section.
DLP via response_replace#
The response_replace rule applies a regex find-and-replace over the JSON-RPC result payload of any tool call before it reaches the agent. The DLP catalog ships seven categories of curated regexes — emails, IBAN and credit cards (Luhn / mod-97 validated), IPs, URLs, vendor API keys, PII patterns, generic high-entropy tokens. Add custom rules with your own pattern + replacement.
{
"rule_type": "response_replace",
"tools": ["*"],
"pattern": "[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}",
"replacement": "***",
"dlp_rule_id": "emails"
}Replacements apply to Streamable HTTP sync responses (the result body of the POST) and to async-mode (where a 202 POST is followed by the result on the open SSE channel) — both paths are inspected and rewritten before the bytes reach the agent.
Cross-call abuse via rate_limit + ban#
rate_limit is the cross-call abuse-detection layer. Scope on match_key = consumer, api_key or ip; set threshold + timespan for the window; reset_expire_on_hit for sliding-window semantics. Escalate from rate-limit to identity ban with ban_after_n_exceeded + ban_timespan — the identity is rejected for the ban duration once it crosses the threshold N additional times.
{
"rule_type": "rate_limit",
"name": "delete-burst",
"tools": ["delete_file", "drop_table"],
"match_key": "consumer",
"threshold": 5,
"timespan": 60,
"reset_expire_on_hit": true,
"ban_after_n_exceeded": 2,
"ban_timespan": 3600
}rate_limit blocks volume abuse for a single identity. It does not reason about a sequence of different tool calls that individually look legitimate but cumulatively constitute an attack — the classic agent-trajectory problem. Sequence-level detection is on the roadmap. For custom multi-step heuristics today, write a Function on phase="access" that maintains state in the request context.
Layering MCP with an LLM Proxy#
For full coverage, route your agent traffic through an AIronClaw LLM Proxy for the model-facing leg AND an MCP Proxy for the tool- facing leg. The LLM Proxy applies prompt_guard (regex catalog + LLM judge, including multimodal coverage when the judge is a vision model) on the prompt; the MCP Proxy applies DLP, rate-limit-with-ban and tool_description_inject on tool I/O. Each layer catches a different attack class and the failures are independent.
Cache#
The static_cache rule stores deterministic responses for a tool keyed on its arguments. Use the cache endpoint when you want to forcibly invalidate cached entries — e.g. after a backend data update.
Purge tool cache#
Query
curl -X DELETE "https://app.aironclaw.com/api/mcp/$ID/cache?tool=list_dir" \
-H "Authorization: Bearer $AIFW_PAT"JWKS test#
Validate JWKS document#
Sanity-checks a JWKS JSON document before you save it on a proxy's auth.jwksJson. Pure-validation: no network calls are made (avoids DNS-rebinding on user-supplied IdP URLs). Always returns 200; check the ok field.
Body
curl -X POST https://app.aironclaw.com/api/mcp/jwks/test \
-H "Authorization: Bearer $AIFW_PAT" \
-H "Content-Type: application/json" \
-d '{ "jwksJson": "{\"keys\":[{\"kid\":\"abc\",\"kty\":\"RSA\",...}]}" }'Upstream JWT (OAuth2 Client Credentials)#
When the caller authenticates with an aifw API key and auth.injectIdentity is on, the proxy needs a JWT to inject as params._meta.aifw.jwt so the upstream workflow can act on-behalf-of the caller. Configure auth.upstreamJwt with your IdP's OAuth2 Client Credentials grant and the firewall fetches the token, caches it (encrypted at rest in Redis, TTL =expires_in - 60s), and injects it on every tools/call.
The JWT path is intentionally not covered by this mechanism: when the caller presents a verified JWT, that JWT is propagated as-is so downstream APIs see the real user identity. Client Credentials is the fallback for the API-key path only.
auth.upstreamJwt fields
The access_token is cached in Redis at aifw:cc_token:<mcp_uuid> as an encrypted blob (same AES-256-GCM envelope used for other persisted secrets). A short-lived lock at aifw:cc_lock:<mcp_uuid> prevents thundering-herd fetches when the cache is cold. On a hard fetch failure (IdP down / 5xx / decrypt error) the proxy returns 502 so a request never silently forwards without the JWT the operator opted in to.
Dry-run a Client Credentials fetch#
Performs a single OAuth2 Client Credentials POST against the given IdP token endpoint, without persisting anything. Used by the dashboard's "Test fetch" button. SSRF-gated on tokenUrl. Always returns 200; check ok in the body.
Body
curl -X POST https://app.aironclaw.com/api/mcp/upstream-jwt/test \
-H "Authorization: Bearer $AIFW_PAT" \
-H "Content-Type: application/json" \
-d '{
"tokenUrl": "https://idp.example.com/oauth/token",
"clientId": "my-client",
"clientSecret": "...",
"audience": "https://api.example.com"
}'