Skip to content

Build1 publisher3 min readPublished

PolicyAware gates read, write and delete separately before the MCP server sees the call

PolicyAware 0.4.4 evaluates identity, tenant, region, arguments and risk tier before an agent's action changes state. Enforcement sits inline on every turn, and the project's own guidance is that adopters measure what that costs.

The Engineer · Build desk

Illustration accompanying PolicyAware gates read, write and delete separately before the MCP server sees the call

What happened

  • PolicyAware is an open-source Python framework that puts deny-by-default policy, PII and secret handling, MCP tool governance, model routing, runtime evaluation and audit evidence in front of an agent's actions.
  • The gateway's inspect_and_mutate call fails closed, raising PermissionError for requests that policy denies or routes to approval, before a provider or a tool is contacted.
  • The write-up declines to promise zero overhead and asks teams to benchmark median, p95 and p99 latency in their own request path before setting production objectives.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint With default: deny in the file, the policy file is the enumerated list of what agents may do: every connector action an agent might want is unreachable until somebody writes and reviews a rule for it.
  • decision require_approval on write_file puts a human in the synchronous path of every agent write. Someone has to own that approval queue, or the agent waits.
  • cost Enforcement runs inline on the common path, so the policy engine's tail latency is added to the tail latency of every tool call, and the adopter has to measure it.
  • exposure Because denial surfaces as PermissionError, an agent with sloppy exception handling converts a policy decision into a crashed run.

Call `inspect_and_mutate` and no provider request happens yet. The gateway loads `policyaware.yaml`, inspects the prompt and the arguments for PII, PHI, secrets and sensitive categories [6], assigns a deterministic risk tier of low, medium, high or critical [7], then evaluates deny, approval, allow and transform rules [8]. Denied and approval-gated work stops before the model or the tool executes [9]. The call raises `PermissionError` [13], so retry logic in the agent has to tell a policy denial apart from a rate limit.

One rule constrains how you write the file. Transform rules do not grant access: per the dev.to write-up, a redaction transform only modifies a request policy already permits, and a denied request stays denied [12].

The MCP example policy declares `default: deny` under `schema_version: "0.2"` [15]. Of the three actions it lists, one reaches the server on the agent's own authority [1]. On a filesystem connector, `read_file` is `allow` at `risk: low` with `side_effect: none`, restricted to the developer and security_engineer roles [16]. `write_file` is `require_approval` at `risk: high` with `side_effect: write`, developers only [17]. `delete_file` is `deny` at `risk: critical` with `side_effect: delete` [18].

Effect, risk and side effect are three separate fields, so a reviewer reads what an action does to external state without inferring it from the verb in the tool name [16][17][18]. The example policy is named `mcp_proxy_policy`, and the framework evaluates a raw JSON-RPC request whose method is `tools/call` and whose `params.name` is `filesystem.read_file` [15][19]. That lets the decision point sit between the client and the server, on the wire format, instead of inside the agent's own code.

On latency, the article declines to promise "zero overhead" and says the aim is to keep the common enforcement path local, deterministic, measurable and separate from optional heavyweight ML integrations [4]. It publishes no numbers of its own and asks teams to benchmark median, p95 and p99 in their own request path before setting production objectives [5]. For a published number to transfer to your path, the heavyweight integrations would have to be off, the policy file comparable in size and rule count, and the engine in the same process; the base package declares four core dependencies, Pydantic, PyYAML, Typer and Rich, none of them a model runtime [3][3].

On an allowed turn the engine runs twice: inspection before execution, then output evaluation for leakage, citations and policy consistency after [2][10]. Both passes are inline, so the policy engine's p99 lands on top of the p99 of every tool call the agent makes.

Each decision emits reason codes, matched policy IDs and audit evidence [11]. A developer whose `write_file` stalled can read the policy ID that matched.

What to watch

  • Whether a release after 0.4.4 publishes measured median, p95 and p99 for the local enforcement path with the optional ML integrations off.
  • Whether the MCP policy schema past "0.2" keeps effect, risk and side_effect as separate fields or collapses them into one.
  • Whether the docs define what happens to an agent left waiting on a require_approval action, including timeout and resume behaviour.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories