Cedar policies
This document provides detailed guidance on writing and configuring Cedar policies for MCP server authorization. You'll learn how to create effective policies, configure authorization settings, and troubleshoot common issues.
For the conceptual overview of authentication and authorization, see Authentication and authorization framework.
Cedar policy language
Cedar policies express authorization rules in a clear, declarative syntax:
permit|forbid(principal, action, resource) when { conditions };
permit
orforbid
: Whether to allow or deny the operationprincipal
: The entity making the request (the client)action
: The operation being performedresource
: The object being accessedconditions
: Optional conditions that must be satisfied
MCP-specific entities
In the context of MCP servers, Cedar policies use the following entities:
Principal
The client making the request, identified by the sub
claim in the JWT token:
- Format:
Client::<client_id>
- Example:
Client::user123
Action
The operation being performed on an MCP feature:
- Format:
Action::<operation>
- Examples:
Action::"call_tool"
: Call a toolAction::"get_prompt"
: Get a promptAction::"read_resource"
: Read a resourceAction::"list_tools"
: List available tools
Resource
The object being accessed:
- Format:
<type>::<id>
- Examples:
Tool::"weather"
: The weather toolPrompt::"greeting"
: The greeting promptResource::"data"
: The data resource
Configuration formats
You can configure Cedar authorization using either JSON or YAML format:
JSON configuration
{
"version": "1.0",
"type": "cedarv1",
"cedar": {
"policies": [
"permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
"permit(principal, action == Action::\"get_prompt\", resource == Prompt::\"greeting\");",
"permit(principal, action == Action::\"read_resource\", resource == Resource::\"data\");"
],
"entities_json": "[]"
}
}
YAML configuration
version: '1.0'
type: cedarv1
cedar:
policies:
- 'permit(principal, action == Action::"call_tool", resource ==
Tool::"weather");'
- 'permit(principal, action == Action::"get_prompt", resource ==
Prompt::"greeting");'
- 'permit(principal, action == Action::"read_resource", resource ==
Resource::"data");'
entities_json: '[]'
Configuration fields
version
: The version of the configuration formattype
: The type of authorization configuration (currently onlycedarv1
is supported)cedar
: The Cedar-specific configurationpolicies
: An array of Cedar policy stringsentities_json
: A JSON string representing Cedar entities
Writing effective policies
Understanding how to write Cedar policies is crucial for securing your MCP servers effectively. This section provides practical guidance for creating policies that match your security requirements.
Basic policy patterns
Start with simple policies and build complexity as needed:
Allow specific tool access
permit(principal, action == Action::"call_tool", resource == Tool::"weather");
This policy allows any authenticated client to call the weather tool. It's useful when you want to provide broad access to specific functionality.
Allow specific user access
permit(principal == Client::"user123", action == Action::"call_tool", resource);
This policy allows a specific user to call any tool. Use this pattern when you need to grant broad permissions to trusted users.
Role-based access control (RBAC)
RBAC policies use roles from JWT claims to determine access:
permit(principal, action == Action::"call_tool", resource) when {
principal.claim_roles.contains("admin")
};
This policy allows clients with the "admin" role to call any tool. RBAC is effective when you have well-defined roles in your organization.
Attribute-based access control (ABAC)
ABAC policies use multiple attributes to make fine-grained decisions:
permit(principal, action == Action::"call_tool", resource == Tool::"sensitive_data") when {
principal.claim_roles.contains("data_analyst") &&
resource.arg_data_level <= principal.claim_clearance_level
};
This policy allows data analysts to access sensitive data, but only if their clearance level is sufficient. ABAC provides the most flexibility for complex security requirements.
Working with JWT claims
JWT claims from your identity provider become available in policies with a
claim_
prefix. You can use these claims in two ways:
On the principal entity:
permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
principal.claim_name == "John Doe"
};
In the context:
permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
context.claim_name == "John Doe"
};
Both approaches work identically. Choose the one that makes your policies more readable.
Working with tool arguments
Tool arguments become available in policies with an arg_
prefix. This lets you
create policies based on the specific parameters of requests:
On the resource entity:
permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
resource.arg_location == "New York" || resource.arg_location == "London"
};
In the context:
permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
context.arg_location == "New York" || context.arg_location == "London"
};
This policy allows weather tool calls only for specific locations, demonstrating how you can control access based on request parameters.
List operations and filtering
List operations (tools/list
, prompts/list
, resources/list
) work
differently from other operations. They're always allowed, but the response is
automatically filtered based on what the user can actually access:
tools/list
shows only tools the user can call (based oncall_tool
policies)prompts/list
shows only prompts the user can get (based onget_prompt
policies)resources/list
shows only resources the user can read (based onread_resource
policies)
You don't need to write explicit policies for list operations. Instead, focus on the underlying access policies, and the lists will be filtered automatically.
For example, if you have this policy:
permit(principal, action == Action::"call_tool", resource == Tool::"weather");
Then tools/list
will only show the "weather" tool for that user.
Policy evaluation and secure defaults
Understanding how Cedar evaluates policies helps you write more effective and secure authorization rules.
Evaluation order
ToolHive's policy evaluation follows a secure-by-default, least-privilege model:
- Deny precedence: If any
forbid
policy matches, the request is denied - Permit evaluation: If any
permit
policy matches, the request is authorized - Default deny: If no policy matches, the request is denied
This means that forbid
policies always override permit
policies, and any
request not explicitly permitted is denied. This approach minimizes risk and
ensures that only authorized actions are allowed.
Designing secure policies
When writing policies, follow these principles:
Start with least privilege: Begin by denying everything, then add specific permissions as needed. This approach is more secure than starting with broad permissions and trying to restrict them.
Use explicit deny sparingly: While forbid
policies can be useful, they can
also make your policy set harder to understand. In most cases, the default deny
behavior is sufficient.
Test your policies: Always test policies with real requests to ensure they work as expected. Pay special attention to edge cases and error conditions.
Advanced policy examples
Combining JWT claims and tool arguments
You can combine JWT claims and tool arguments in your policies to create more sophisticated authorization rules:
permit(principal, action == Action::"call_tool", resource == Tool::"sensitive_data") when {
principal.claim_roles.contains("data_analyst") &&
resource.arg_data_level <= principal.claim_clearance_level
};
This policy allows clients with the "data_analyst" role to access the sensitive_data tool, but only if their clearance level (from JWT claims) is sufficient for the requested data level (from tool arguments).
Multi-tenant environments
In multi-tenant environments, you can use policies to isolate tenants:
permit(principal, action, resource) when {
principal.claim_tenant_id == resource.tenant_id
};
This ensures that clients can only access resources belonging to their tenant.
Data sensitivity levels
For data with different sensitivity levels:
permit(principal, action == Action::"call_tool", resource == Tool::"data_access") when {
principal.claim_clearance_level >= resource.arg_data_sensitivity
};
This ensures that clients can only access data within their clearance level.
Geographic restrictions
For geographically restricted resources:
permit(principal, action == Action::"call_tool", resource == Tool::"geo_restricted") when {
principal.claim_location in ["US", "Canada", "Mexico"]
};
This restricts access based on the client's location.
Time-based access
For resources that should only be accessible during certain hours:
permit(principal, action == Action::"call_tool", resource == Tool::"business_hours") when {
context.current_hour >= 9 && context.current_hour <= 17
};
This restricts access to business hours only.
Entity attributes
Cedar entities can have attributes that can be used in policy conditions. The authorization middleware automatically adds JWT claims and tool arguments as attributes to the principal entity.
You can also define custom entities with attributes in the entities_json
field
of the configuration file:
{
"version": "1.0",
"type": "cedarv1",
"cedar": {
"policies": [
"permit(principal, action == Action::\"call_tool\", resource) when { resource.owner == principal.claim_sub };"
],
"entities_json": "[
{
\"uid\": \"Tool::weather\",
\"attrs\": {
\"owner\": \"user123\"
}
}
]"
}
}
This configuration defines a custom entity for the weather tool with an owner
attribute set to user123
. The policy allows clients to call tools only if they
own them.
Troubleshooting policies
When policies don't work as expected, follow this systematic approach:
Request is denied unexpectedly
- Check policy syntax: Ensure your policies are correctly formatted and use valid Cedar syntax.
- Verify entity matching: Confirm that the principal, action, and resource in your policies match the actual values in the request.
- Test conditions: Check that any conditions in your policies are satisfied by the request context.
- Remember default deny: If no policy explicitly permits the request, it will be denied.
JWT claims are not available
- Verify JWT middleware: Ensure that JWT authentication is configured correctly and running before authorization.
- Check token claims: Verify that the JWT token contains the expected claims.
- Use correct prefix: Remember that JWT claims are available with a
claim_
prefix.
Tool arguments are not available
- Check request format: Ensure that tool arguments are correctly specified in the request.
- Use correct prefix: Remember that tool arguments are available with an
arg_
prefix. - Verify argument names: Confirm that the argument names in your policies match those in the actual requests.
Related information
- For the conceptual overview, see Authentication and authorization framework
- For detailed Cedar policy syntax, see Cedar documentation