Cookbook
Short, tested recipes for common developer tasks with MAQPNA, from read-only tools and approval thresholds to spend caps, DLP, revocations and evidence export.
Each recipe is one task: the goal, the commands or code, and what you see when it works. Policy recipes were checked with maqpna validate and maqpna policy test; the runtime recipes were run against a local MAQPNA (maqpna dev up). For the admin-API commands on a laptop, load the local admin token first with eval "$(maqpna dev env)"; in a cluster, pass --gateway "$GW" or use a context.
1. Allow one MCP server read-only#
Goal: the agent may read from github and nothing else.
apiVersion: maqpna.com/v1alpha1
kind: ToolPolicy
metadata: {name: triage-policy, namespace: team-a}
spec:
agents: ["triage"]
defaultAction: deny
rules:
- name: github-read
servers: [github]
tools: ["get_*", "list_*", "search_*"]
action: allow
maqpna policy test --policy triage-policy.yaml --ns team-a --agent triage --server github --tool get_issue --expect allow
maqpna policy test --policy triage-policy.yaml --ns team-a --agent triage --server github --tool create_issue --expect deny
Result: both exit 0. create_issue is denied with reason default_action, and tools/list no longer shows it to the agent.
2. Require approval above an amount#
Goal: small refunds go through; refunds above 1000 wait for a person.
rules:
- name: big-refund-needs-approval
servers: [payments]
tools: [create_refund]
when:
- {arg: amount, op: gt, value: "1000"}
action: require_approval
reason: Refunds above 1000 need a human.
- name: small-refund
servers: [payments]
tools: [create_refund]
action: allow
maqpna policy test --policy payments.yaml --ns team-a --agent payer --server payments --tool create_refund --args '{"amount": 2500}'
Result: "action": "require_approval", rule big-refund-needs-approval; with {"amount": 20} it is allow, rule small-refund. Other operators: eq, neq, lt, lte, gte, in, nin, glob, regex, exists, absent, cidr, len_gt, len_lt. Two approvers: add approval: {minApprovers: 2}.
3. Keep an agent out of production namespaces#
Goal: deny any call whose namespace argument starts with prod-, whatever the tool.
rules:
- name: no-prod-namespaces
servers: [kubernetes]
tools: ["*"]
argMatch:
namespace: "prod-*"
action: deny
reason: Agents may not touch production namespaces.
- name: k8s-read
servers: [kubernetes]
tools: ["get", "list", "describe", "logs"]
action: allow
Result: get with {"namespace":"prod-eu"} is denied by no-prod-namespaces; with {"namespace":"staging"} it is allowed by k8s-read. If the deny rule came second, the allow rule would match first.
4. Freeze a tool at the weekend#
Goal: no payouts on Saturday or Sunday, Berlin time.
- name: no-weekend-payouts
servers: [payments]
tools: [create_payout]
schedule: {timezone: Europe/Berlin, windows: ["Sat,Sun"]}
action: deny
reason: No payouts at the weekend.
maqpna policy test --policy payments.yaml --ns team-a --agent payer --server payments --tool create_payout --at 2026-10-17T12:00:00Z
Result: on Saturday 17 October the decision is deny with rule no-weekend-payouts; with --at 2026-10-19T12:00:00Z (a Monday) the next rule decides.
5. Rate-limit a tool#
Goal: at most 10 calls a minute to a tool.
- name: small-refund
servers: [payments]
tools: [create_refund]
action: allow
maxCallsPerMinute: 10
Result: the eleventh call in a minute gets -32001 with reason rate_limited. Back off and retry in your code (see Handle decisions).
6. Cap an agent's daily spend#
Goal: the agent may spend at most 2 per session and 50 per day.
apiVersion: maqpna.com/v1alpha1
kind: Agent
metadata: {name: triage, namespace: team-a}
spec:
# ...
budget:
perSessionUSD: "2"
perDayUSD: "50"
model:
# ...
tokensPerMinute: 20000
maxTokensPerRequest: 4096
maqpna apply -f manifests/agent.yaml -n team-a
maqpna budgets list --gateway "$GW"
maqpna budgets list --gateway "$GW" --exceeded -o json
Result: once spent, tool calls get budget_exceeded:agent/day and model calls get HTTP 402 maqpna_budget_exceeded until the window resets. The amounts are in the gateway's budget currency. Namespace and user limits go in a BudgetPolicy (see Budgets and cost limits).
7. Block secrets in prompts and tool arguments#
Goal: a call that contains an AWS access key or a GitHub token never leaves the gateway.
cat > dlp.json <<'EOF'
{"detectors": ["aws_access_key", "github_token", "email"], "requestAction": "deny", "responseAction": "redact"}
EOF
maqpna dev down && maqpna dev up --dlp-profile dlp.json
maqpna dev run -- maqpna call --server echo --tool echo --arg text="key AKIAIOSFODNN7EXAMPLE"
Result:
error -32001: denied by DLP profile dev: dlp:aws_access_key
data: {"domain":"maqpna.com","policy":"dlp","reason":"dlp:aws_access_key","rule":"dev"}
The same profile covers model calls. In a cluster, profiles are gateway configuration (Helm values), bound per server, namespace or model route (Agent.spec.model.dlpProfile); see DLP profiles.
8. Pin the tools you reviewed#
Goal: a tool whose definition changes later is hidden and denied.
maqpna mcp tools dev/echo
maqpna mcp pin dev/echo --mode enforce --out pins.yaml
Result: pins.yaml holds a toolPins block with one sha256: per tool; paste it into the ToolPolicy. After that, a drifted tool is denied with tool_definition_changed, and maqpna mcp tools dev/echo --drift-only lists it.
9. Kill a runaway session#
Goal: stop one session now.
maqpna kill session fix-4821 -n team-a --reason "INC-7: loop calling create_issue" --terminate --gateway "$GW"
Result: every further call of that session is denied within about a second with reason revoked; --terminate also ends its sandbox. Locally, without --terminate:
revoked (block, namespace dev): breakglass/6f5ce165d1dc82f5
error -32001: revoked: calls of this token are blocked by revocation/breakglass/6f5ce165d1dc82f5 (INC-1: test)
Use agent NAME or user NAME instead of session NAME to stop more. See Kill switch and revocations.
10. Revoke one leaked token#
Goal: a session token was printed in a CI log. Block that token only.
JTI=$(maqpna token inspect "$LEAKED_TOKEN" -o json | jq -r .claims.jti)
maqpna kill --gateway "$GW" --jti "$JTI" --reason "token leaked in CI log" --ttl 2h --yes
Result: revoked (block, all namespaces): breakglass/cb12701383deda31; calls with that token exit with status 3. The --ttl can be short, because session tokens live at most 15 minutes in a cluster.
11. Rotate a token#
Goal: replace credentials without downtime.
- Session tokens rotate by themselves: the operator mints a new one at 80% of its lifetime into the same Secret, and the SDKs re-read the file on every request. Make sure your code does not cache the token (see Connect to the gateway).
- Locally, a token lives as long as
maqpna dev run --ttl(default1h); start a newmaqpna dev runfor a new one. - The broker's signing key is rotated by the platform team with
maqpna keys rotate identity -n maqpna-system, then--finishafter the grace period.
Result: maqpna token inspect on the new token shows a new jti and exp; maqpna events SESSION -n NS shows TokenRefreshed.
12. Lift a revocation#
Goal: the incident is over; let the agent run again.
maqpna revocations list --gateway "$GW"
maqpna revocations delete --gateway "$GW" breakglass/cb12701383deda31 --yes
Result: deleted breakglass/cb12701383deda31. The next call is decided by policy again.
13. Export evidence for one session#
Goal: hand an auditor what a session did, in a form they can verify.
maqpna audit export .maqpna/audit.jsonl --session dev-curl2 --out evidence.json # local ledger file
maqpna audit export --gateway "$GW" --session fix-4821 --out evidence.json # cluster
maqpna audit verify .maqpna/audit.jsonl
Result: evidence.json (schema maqpna.io/audit-evidence/v1) holds the session's records, the record count and the chain head; audit verify prints OK: 21 records, hash chain intact. For signed, framework-mapped packs, use maqpna evidence generate (see Evidence packs).
14. Wait for the next approval in a script#
Goal: a script blocks until a call is held, then shows it.
maqpna approvals watch --exit-on-first
maqpna approvals list
maqpna approvals approve apr_31bc56b1ef96a592333824db --approver reviewer@localhost --note "checked"
Result: watch returns as soon as an approval is pending; after approve, the held call runs exactly once. A second use of the same approval is denied with approval_invalid.
15. Test as another user or group#
Goal: check a rule that depends on who the agent acts for.
maqpna dev run --user [email protected] --agent payer --namespace team-a -- python agent.py
maqpna policy test --policy payments.yaml --ns team-a --agent payer --user [email protected] \
--group treasury --server payments --tool create_refund --args '{"amount": 2500}'
Result: the token's act.sub is user:[email protected], and rules with principals match on it. Approvers can never approve calls made on their own behalf.
Next steps#
- Write policies, every rule field
- Handle decisions in code
- Debug and observe
- Error and reason codes