Barndoor uses a policy-based access control system to define what MCP tools and resources each agent can access. This guide covers how to manage policies programmatically via the API.
Policy Structure
Policy Hierarchy
Policies follow a hierarchical structure:
| Policy Type | Format | Description |
|---|
| Server-level (base) | resource.{server_id}.vdefault | Applies to ALL agents |
| Agent-scoped | resource.{server_id}.vdefault/{agent_id} | Applies to specific agent only |
Agent-scoped policies override their parent server-level policy. If an agent has a scoped policy with EFFECT_ALLOW, it will be allowed even if the base policy is EFFECT_DENY.
Actions follow the MCP convention:
tools/call:{tool_name} # For tool invocations
resources/read:{resource} # For resource reads
* # Wildcard - all actions
Examples:
tools/call:chat_postMessage - Slack’s post message tool
tools/call:get_accounts - Salesforce accounts tool
* - All tools and resources
Common Operations
Turn Main Toggle ON (Allow All)
To enable all tools for an agent on a server:
curl -X POST "$BASE_URL/api/policy" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policies": [{
"apiVersion": "api.cerbos.dev/v1",
"resourcePolicy": {
"resource": "SERVER_ID",
"version": "default",
"scope": "AGENT_ID",
"rules": [{
"name": "allow_all",
"effect": "EFFECT_ALLOW",
"actions": ["*"],
"roles": ["*"]
}]
}
}]
}'
Turn Main Toggle OFF (Deny All)
To disable all tools:
curl -X POST "$BASE_URL/api/policy" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policies": [{
"apiVersion": "api.cerbos.dev/v1",
"resourcePolicy": {
"resource": "SERVER_ID",
"version": "default",
"scope": "AGENT_ID",
"rules": [{
"name": "allow_all",
"effect": "EFFECT_DENY",
"actions": ["*"],
"roles": ["*"]
}]
}
}]
}'
To allow everything except a specific tool:
curl -X POST "$BASE_URL/api/policy" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policies": [{
"apiVersion": "api.cerbos.dev/v1",
"resourcePolicy": {
"resource": "SERVER_ID",
"version": "default",
"scope": "AGENT_ID",
"rules": [
{
"name": "allow_all",
"effect": "EFFECT_ALLOW",
"actions": ["*"],
"roles": ["*"]
},
{
"name": "chat_postMessage",
"effect": "EFFECT_DENY",
"actions": ["tools/call:chat_postMessage"],
"roles": ["*"]
}
]
}
}]
}'
Add a Restriction with Condition
Restrictions let you apply fine-grained conditions:
curl -X POST "$BASE_URL/api/policy" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policies": [{
"apiVersion": "api.cerbos.dev/v1",
"resourcePolicy": {
"resource": "SERVER_ID",
"version": "default",
"scope": "AGENT_ID",
"rules": [
{
"name": "allow_all",
"effect": "EFFECT_ALLOW",
"actions": ["*"],
"roles": ["*"]
},
{
"name": "block_general_channel",
"effect": "EFFECT_DENY",
"actions": ["tools/call:chat_postMessage"],
"roles": ["*"],
"condition": {
"match": {
"all": {
"of": [
{ "expr": "request.resource.attr.channel == \"general\"" }
]
}
}
}
}
]
}
}]
}'
Toggle a Restriction ON/OFF
To toggle a restriction, change its effect between EFFECT_DENY (active) and EFFECT_ALLOW (inactive):
Turn restriction OFF (keep the rule but make it allow):
{
"name": "block_general_channel",
"effect": "EFFECT_ALLOW", // Changed from EFFECT_DENY
"actions": ["tools/call:chat_postMessage"],
"roles": ["*"],
"condition": { ... }
}
Turn restriction ON (set effect back to deny):
{
"name": "block_general_channel",
"effect": "EFFECT_DENY", // Active - blocking
"actions": ["tools/call:chat_postMessage"],
"roles": ["*"],
"condition": { ... }
}
Reading Policies
Get Policy by ID
# URL-encode the policy ID (especially the / character)
POLICY_ID="resource.SERVER_ID.vdefault/AGENT_ID"
ENCODED_ID=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$POLICY_ID', safe=''))")
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/api/policy?id=$ENCODED_ID"
List Policies for an Agent
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/api/policies?agent_id=AGENT_ID"
List Policies for a Server
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/api/policies?server_id=SERVER_ID"
Policy Enable/Disable (API-level)
The enable/disable endpoints affect the entire policy at the engine level. This is different from toggling individual rules via EFFECT_ALLOW/EFFECT_DENY.
Disable a Policy
POLICY_ID="resource.SERVER_ID.vdefault/AGENT_ID"
ENCODED_ID=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$POLICY_ID', safe=''))")
curl -X PUT -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/api/policy/disable?id=$ENCODED_ID"
Enable a Policy
curl -X PUT -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/api/policy/enable?id=$ENCODED_ID"
You cannot disable a server-level (base) policy if agent-scoped policies depend on it. This would break the policy inheritance chain. Disable child policies first.
Best Practices
- Use scoped policies for agent-specific rules - Server-level policies affect all agents
- Name rules descriptively - Use
allow_all, tool names, or descriptive restriction names
- Toggle restrictions via effect - Change
EFFECT_DENY ↔ EFFECT_ALLOW rather than deleting rules
- Always include
allow_all as first rule - Other rules modify this baseline
- URL-encode policy IDs - The
/ in scoped policy IDs must be encoded as %2F