Cap an agent beyond its scopes
Scopes say WHICH tools an agent can call. A declarative policy says HOW — a capped amount, an allowed recipient domain — even when the scope alone would allow it.
- 1
On the MCP lab’s policy panel, add a DENY rule on transfer_funds with an amount constraint (e.g. max 500) for one of your agents that holds payments.write.
Holding a scope is necessary but not sufficient for real least privilege — the policy engine composes as a strict AND on top of the scope gate: it can only ever tighten what the scope check already allowed, never loosen it.
- 2
Invoke transfer_funds with an amount under the cap, then again over it.
The under-cap call is authorized exactly as before (no policies = unchanged behavior); the over-cap call is refused as mcp.policy_denied BEFORE the simulated tool ever runs — the same "authorize, then execute" discipline as the scope gate itself.
- 3
Flip that agent’s default mode to deny, then invoke a different tool it has no RULE for — but one it DOES hold the scope for (the tool picker marks the ones it does not).
With no matching rule, the configured default mode decides — "allow" preserves today’s behavior, but "deny" is real least-privilege: nothing runs for that agent unless a rule explicitly permits it. The in-scope part is load-bearing, not fussiness: the scope check runs BEFORE the policy engine, so a tool your agent has no scope for is refused as scope_denied and your rules are never consulted at all. Both denials look the same on screen, and only the policy one is this lesson (or completes this practical).
- 4
Now notice that EVERY call from that agent is refused — including the transfer_funds amounts your own rule was written to permit — and fix it by adding an ALLOW rule for the one tool and shape it should still be able to call.
This is the half of least privilege that "flip to deny" alone does not teach: a DENY rule does not imply an allow for its complement. Under default-deny an agent holds no capability at all until a rule grants one, so the allowlist is the policy and the denies are only exceptions carved out of it. Watch the allowed shape work again while everything else stays refused — that is the difference between a kill switch and a permission model.
- 5
Run the sample-invocation trace on the policy panel and read the rule-by-rule decision.
The trace shows exactly which rule matched (or that none did, falling through to the default mode) — a human-readable, code-decided audit trail, the same "show your work" the FGA Check trace gives you.
Learn the theory
🩻 X-ray — what actually happened
Your own insert-only audit trail — the real server events, sanitized (never a secret), each linked to the lesson that explains it.