ToolPolicy
ToolPolicy (maqpna.com/v1alpha1): every field, type, default and validation rule, generated from the CRD schema.
ToolPolicy is enforced by the MAQPNA gateway on every MCP tool call.
At a glance#
| Property | Value |
|---|---|
| API version | maqpna.com/v1alpha1 |
| Kind | ToolPolicy |
| Scope | Namespaced |
| Plural | toolpolicies |
| Short names | tp |
| Categories | maqpna |
| Status subresource | yes |
kubectl get toolpolicies -A
kubectl get tp <name> -o yaml
kubectl explain toolpolicies.spec
Columns in kubectl get#
| Column | Type | From |
|---|---|---|
| Default | string | .spec.defaultAction |
| Enforcement | string | .spec.enforcement |
| Ready | string | .status.conditions[?(@.type=="Ready")].status |
| Age | date | .metadata.creationTimestamp |
Example#
From config/samples/toolpolicy-coder.yaml:
apiVersion: maqpna.com/v1alpha1
kind: ToolPolicy
metadata:
name: coder-policy
namespace: team-a
spec:
agents: ["coder", "coder-*"]
defaultAction: deny
rules:
- name: github-read
servers: [github]
tools: ["get_*", "list_*", "search_*"]
action: allow
maxCallsPerMinute: 120
- name: github-write-needs-approval
servers: [github]
tools: ["create_pull_request", "merge_pull_request"]
action: require_approval
reason: Writes to source control require human approval.
- name: k8s-read
servers: [kubernetes]
tools: ["get", "list", "describe", "logs"]
action: allow
maxCallsPerMinute: 60
- name: k8s-no-prod-mutations
servers: [kubernetes]
tools: ["apply", "delete", "scale", "patch"]
argMatch:
namespace: "prod-*"
action: deny
reason: Mutations in production namespaces are forbidden for agents.
From config/samples/toolpolicy-sre.yaml:
# ToolPolicy of the sre agent (agent-sre.yaml) for the gateway's built-in
# maqpna-k8s server: diagnose freely (rate-limited), change nothing without
# a human, never touch kube-system or production namespaces. The gateway
# additionally holds every applied write for approval
# (gateway.k8sOps.writes.approval: required) and the impersonated
# ServiceAccount's RBAC is read-only unless writes are opted in.
apiVersion: maqpna.com/v1alpha1
kind: ToolPolicy
metadata:
name: sre-policy
namespace: sre
spec:
agents: ["sre"]
defaultAction: deny
rules:
- name: no-system-or-prod-writes
servers: [maqpna-k8s]
tools: ["scale_workload", "rollout_restart", "delete_pod"]
argMatch:
namespace: "kube-*"
action: deny
reason: Agents never change system namespaces.
- name: no-prod-writes
servers: [maqpna-k8s]
tools: ["scale_workload", "rollout_restart", "delete_pod"]
argMatch:
namespace: "prod-*"
action: deny
reason: Production changes go through the change process, not an agent.
- name: diagnose
servers: [maqpna-k8s]
tools: ["list_resources", "get_resource", "describe_resource", "list_events", "get_resource_usage", "explain_failure", "get_pod_logs"]
action: allow
maxCallsPerMinute: 120
- name: remediate-with-approval
servers: [maqpna-k8s]
tools: ["scale_workload", "rollout_restart", "delete_pod", "cordon_node", "uncordon_node"]
action: require_approval
maxCallsPerMinute: 10
reason: Every cluster change needs an on-call engineer's approval.
Spec fields#
ToolPolicySpec governs which MCP tool calls agents may make.
| Field | Type | Required | Description |
|---|---|---|---|
agents |
[]string | agents this policy applies to (globs over agent names). | |
cedar |
string | cedar is optional Cedar policy text (F-24) evaluated together with rules; the most restrictive decision wins and defaultAction applies only when neither a rule nor a Cedar policy is determining. Schema: config/cedar/maqpna.cedarschema. Gateways built without the Cedar adapter (-tags cedar) deny every call this policy applies to. Max length: 65536. |
|
defaultAction |
string | DefaultAction is the fallback verdict when no rule matches. One of: allow, deny. Default: "deny". |
|
dryRunFallback |
string | dryRunFallback: what a dryRun policy contributes: allow (default) or defaultAction. One of: allow, defaultAction. |
|
enforcement |
string | enforcement: enforce (default) or dryRun. A dryRun policy is evaluated and its would-be decisions are audited (ext.dryRun=true), but it only contributes dryRunFallback to the enforced decision. One of: enforce, dryRun. |
|
rules |
[]object | Max items: 256. |
|
rules[].action |
string | yes | PolicyAction is the gateway's verdict for a tool call. One of: allow, deny, require_approval. |
rules[].approval |
object | approval sets approval requirements (require_approval only). | |
rules[].approval.approverGroups |
[]string | approverGroups whose members may approve (globs). Max items: 16. |
|
rules[].approval.minApprovers |
integer (int32) | minApprovers distinct approvers required (default 1). Minimum: 1. Maximum: 10. |
|
rules[].approval.userConfirmation |
string | userConfirmation "ciba" requires the delegating end user's own confirmation on their device (OpenID CIBA to the customer IdP, F-30) instead of approver votes; approvers may still deny. When CIBA is unavailable the gateway falls back per its ciba.fallback setting. One of: ciba. |
|
rules[].argMatch |
map[string]string | argMatch maps argument JSON paths to glob/regex patterns. | |
rules[].maxCallsPerMinute |
integer (int32) | maxCallsPerMinute caps the calls matching this rule per session over a sliding 60 s window; further calls are denied with reason rate_limited (0 = unlimited). Rate unit: calls (MCP tools/call, A2A messages; docs/api-conventions.md, "Rates"). Minimum: 0. |
|
rules[].name |
string | yes | Min length: 1. |
rules[].principals |
object | principals restricts the rule to human principals (policy v2). Rule: at least one of users, groups, notUsers, notGroups is required. | |
rules[].principals.groups |
[]string | Max items: 64. |
|
rules[].principals.notGroups |
[]string | Max items: 64. |
|
rules[].principals.notUsers |
[]string | Max items: 64. |
|
rules[].principals.users |
[]string | Max items: 64. |
|
rules[].reason |
string | ||
rules[].schedule |
object | schedule restricts when the rule matches. Rule: windows or exceptWindows is required. | |
rules[].schedule.exceptWindows |
[]string | Max items: 32. |
|
rules[].schedule.timezone |
string | timezone is an IANA name (default UTC). Max length: 64. |
|
rules[].schedule.windows |
[]string | Max items: 32. |
|
rules[].scopes |
[]string | scopes the agent token must all hold (globs in granted scopes apply). Max items: 32. |
|
rules[].servers |
[]string | servers (MCP server / tool names, globs allowed). | |
rules[].sessionTaints |
object | sessionTaints matches the session's taint labels (F-13). Rule: any or all is required. | |
rules[].sessionTaints.all |
[]string | Max items: 16. |
|
rules[].sessionTaints.any |
[]string | Max items: 16. |
|
rules[].sinks |
[]string | sinks matches tools labelled as exfiltration sinks (toolLabels.sinks); globs allowed. Max items: 16. |
|
rules[].substrate |
object | substrate matches where the session runs, as the identity broker verified it before minting (token claim maqpna_substrate); a session no check ran for has isolation and attestation "none". Rule: substrate needs at least one of isolation, notIsolation, attestation, notAttestation, checks. | |
rules[].substrate.attestation |
[]string | attestation: the rule applies only to these levels. Max items: 3. |
|
rules[].substrate.checks |
[]string | checks: the rule applies only to sessions verified by these identity checks (e.g. kosa8). Max items: 8. |
|
rules[].substrate.isolation |
[]string | isolation: the rule applies only to these classes. Max items: 7. |
|
rules[].substrate.notAttestation |
[]string | notAttestation: the rule does not apply to these levels. Max items: 3. |
|
rules[].substrate.notIsolation |
[]string | notIsolation: the rule does not apply to these classes. Max items: 7. |
|
rules[].tools |
[]string | tools (MCP tool names, globs allowed). | |
rules[].when |
[]object | when lists typed argument conditions; all must hold. Max items: 32. |
|
rules[].when[].arg |
string | yes | arg is a top-level argument name or a dotted path (a.b.c). Min length: 1. Max length: 256. |
rules[].when[].op |
string | yes | ConditionOperator is a typed argument operator. One of: eq, neq, glob, regex, in, nin, lt, lte, gt, gte, exists, absent, cidr, len_gt, len_lt. |
rules[].when[].value |
string | Max length: 1024. |
|
rules[].when[].values |
[]string | Min items: 1. Max items: 256. |
|
toolLabels |
[]object | toolLabels label tools as taint sources, exfiltration sinks or taint cleaners (F-13). Max items: 64. |
|
toolLabels[].clearTaints |
[]string | Max items: 16. |
|
toolLabels[].servers |
[]string | Max items: 64. |
|
toolLabels[].sinks |
[]string | Max items: 16. |
|
toolLabels[].taints |
[]string | Max items: 16. |
|
toolLabels[].tools |
[]string | Max items: 256. |
|
toolPins |
[]object | toolPins pin MCP tool definitions per server (F-04). Max items: 64. |
|
toolPins[].allowUnpinned |
boolean | allowUnpinned lets tools without a pin through in enforce mode. | |
toolPins[].mode |
string | mode: off | learn (default: record and audit, never block) | enforce. One of: off, learn, enforce. |
|
toolPins[].onChange |
string | onChange in enforce mode: hide (default: drop from tools/list and deny calls) | approve (require approval) | alert (allow and audit). One of: hide, approve, alert. |
|
toolPins[].pins |
map[string]string | ||
toolPins[].server |
string | yes | server is the MCP server name (as in /mcp/{server}). Min length: 1. Max length: 63. |
- Rule on
spec: dryRunFallback requires enforcement dryRun
Status fields#
ToolPolicyStatus is the observed state of a ToolPolicy.
The operator writes status; do not set it yourself.
| Field | Type | Required | Description |
|---|---|---|---|
conditions |
[]object | ||
conditions[].lastTransitionTime |
string (date-time) | yes | lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. |
conditions[].message |
string | yes | message is a human readable message indicating details about the transition. This may be an empty string. Max length: 32768. |
conditions[].observedGeneration |
integer (int64) | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. Minimum: 0. |
|
conditions[].reason |
string | yes | reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. Min length: 1. Max length: 1024. Pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$. |
conditions[].status |
string | yes | status of the condition, one of True, False, Unknown. One of: True, False, Unknown. |
conditions[].type |
string | yes | type of condition in CamelCase or in foo.example.com/CamelCase. Max length: 316. Pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$. |
observedGeneration |
integer (int64) |