DEV Community

Zira
Zira

Posted on

Your MCP Server Needs a Capability Budget, Not Just Auth

Most MCP security checklists stop at “is this caller authenticated?” That is necessary, but it does not answer the operational question: what is this tool allowed to do during this run?

A useful boundary is a capability budget: a short-lived, explicit contract for each tool invocation. It should constrain the action, target, resource, quantity, and expiry. The model can request a tool, but the runtime decides whether the request fits the contract.

1. Define the budget next to the tool

Start with a deliberately boring schema:

type CapabilityBudget = {
  runId: string;
  tool: string;
  actions: string[];
  resources: string[];
  maxCalls: number;
  maxBytes?: number;
  expiresAt: string;
  approval: "none" | "human";
  policyVersion: string;
};
Enter fullscreen mode Exit fullscreen mode

For example, a code-review run might receive:

{
  "runId": "run_8f2",
  "tool": "github.create_comment",
  "actions": ["comment"],
  "resources": ["repo:acme/api:pull:481"],
  "maxCalls": 1,
  "expiresAt": "2026-08-14T03:00:00Z",
  "approval": "human",
  "policyVersion": "p17"
}
Enter fullscreen mode Exit fullscreen mode

The important part is what is absent: no repository-wide write permission, no issue creation, and no unbounded retry allowance.

2. Enforce it at dispatch time

Do not check the budget only when the agent plans the call. The queue, worker, and tool adapter should all treat the budget as untrusted input and revalidate it.

A dispatch decision can be reduced to:

  1. Load the current run and budget.
  2. Verify the budget is unexpired.
  3. Verify the requested action and resource match exactly.
  4. Atomically reserve one call and any byte quota.
  5. Recheck the current policy and credential version.
  6. Dispatch with the reservation ID.

If steps 4 and 5 cannot be made durable, a worker crash can turn one approved call into several attempts. That is a reliability bug as well as a security bug.

3. Separate planning from spending

Let the model propose a sequence, but make the runtime spend from the budget one reservation at a time. A plan such as “inspect, patch, test, notify” should not silently inherit write capability from the first step.

A minimal ledger makes this visible:

reservation tool requested decision outcome
r1 repo.read 1 call allowed confirmed
r2 repo.write 1 call approval required pending
r3 slack.send 1 call denied not dispatched

Keep pending and unknown distinct. Pending means no dispatch has been recorded. Unknown means dispatch may have happened but confirmation was lost. Only the latter requires provider lookup or an idempotency-key reconciliation before retry.

4. Test the failure modes

A capability budget is only real if it survives interruptions. Inject at least these cases:

  • The worker pauses after reservation but before dispatch.
  • The provider times out after accepting the request.
  • The policy changes while a job is queued.
  • The model changes the resource identifier between planning and dispatch.
  • Two workers race for the last allowed call.
  • A retry arrives after the budget expires.
  • A child tool asks for a broader capability than its parent run owns.

For every case, record the expected ledger state, whether a provider lookup is required, and whether a human must reapprove. A green test suite that never forces an unknown outcome is testing the happy path, not the boundary.

5. Make the hosting boundary explicit

If this runtime must stay available for queued work or browser-assisted tasks, hosting is part of the control plane. A managed runtime such as managed OpenClaw hosting on Ampere can be evaluated as one deployment option, but it does not replace capability checks, prompt-injection defenses, credential scoping, or reconciliation logic.

The questions to verify are practical: where durable run state lives, how workers restart, how credentials are mounted, how logs are retained, and how you rebuild the same policy version on a clean host. Treat the hosting choice as an availability and recovery decision, not as an authorization decision.

A compact acceptance checklist

Before calling an MCP integration production-ready, verify:

  • Every tool call has a run-bound, expiring capability budget.
  • Action and resource are matched at dispatch, not only during planning.
  • Call and byte limits are reserved atomically.
  • Policy and credential versions are rechecked before execution.
  • Parent and child tools cannot expand one another’s authority.
  • Timeouts produce unknown, not an automatic retry.
  • Provider IDs and idempotency keys support reconciliation.
  • Expiry, revocation, and clean-host restore are tested.

Authentication answers “who is asking?” A capability budget adds “what may happen, where, how often, and until when?” That is the boundary worth reviewing.

Top comments (2)

Collapse
 
reidmarlow profile image
Reid Marlow

The dispatch-time check is the part I would not let slip. If the worker only trusts the planner's budget, retries and queue replay become a quiet permission escalator. I would also log the denied spend attempts, because that is where the useful attack telemetry shows up.

Collapse
 
mads_hansen_27b33ebfee4c9 profile image
Mads Hansen

Strong framing. One implementation detail I’d add is canonical resource identity. “Exact match” is only as strong as the normalization behind it: repo aliases, URL encodings, path traversal, symlinks, tenant-qualified IDs, and case rules can turn two strings into the same target—or one string into a different target after dispatch.

I’d have the policy layer resolve the target once, bind the budget and reservation to that immutable identity plus policy digest, then make the adapter prove it is using the same identity. Delegated/child budgets should be mechanically intersected with the parent, never copied and edited.

A useful failure test is: reserve against one alias, mutate routing or a symlink, then dispatch through another alias. The call should fail closed before network or filesystem I/O. That makes the budget an enforceable capability, not just a well-structured intent record.