Tutorial

Governing tool risk

Why "the LLM can call your API" is not a yes/no question — and how trust boundaries plus per-tool risk levels give you a graduated answer instead.

The problem: one API, many trust levels

Handing an LLM agent a REST API is not one decision, it's several, stacked. Who is allowed to reach this server at all? Which operations on it are safe to run without a human watching, and which ones — refunds, deletions, sending real messages to real people — need a human to confirm or approve first? Treating every tool the same way ("the agent can call it" or "it can't") throws away the information you actually have about how dangerous each operation is.

This platform splits the problem into two independent, composable controls: a trust boundary on the server (who can reach it), and a risk level on each tool (what happens when they do).

Server trust boundaries

Every MCP server you create is assigned exactly one trust boundary at creation time:

  • internal — for your own team. Tightest reach; the right default for anything touching systems that were never designed to be internet-facing.
  • partner — shared with vetted third parties, generally gated behind OAuth so you know exactly which external identity is calling.
  • public — listed in the marketplace and reachable by anyone with the URL, held to the highest scrutiny before publish.

The boundary is a coarse, structural gate — it answers "should this server exist in front of this audience at all," before any individual tool call is evaluated. An internal HR system has no business being a public server no matter how carefully each tool inside it is scoped; the boundary stops that mistake at the point of server creation, in the console's new-server wizard, not after the fact.

Boundary is about audience, not about safety per call. A public server can still be entirely read-only and low-risk (see the gallery of live public servers, all no-auth and read-only). An internal server can still contain a destructive tool that needs approval every time. The two controls are deliberately orthogonal.

Per-tool risk levels

Inside a server, every generated tool carries its own risk classification, derived when tools are generated from an OpenAPI contract and overridable by hand afterward:

  • low (read-only)GET/HEAD/OPTIONS operations. No side effects.
  • medium (financial / side-effecting) — writes that aren't destructive: creating a record, sending a message, charging a card.
  • high (destructive)DELETE operations, or any operation whose path or operation id contains a destructive verb (delete, remove, purge, terminate, revoke, wipe).

This classification isn't cosmetic — it's derived directly from the HTTP method and operation naming at generation time:

// simplified from the platform's tool-generation pipeline function riskFor(method, operationId, path) { const hay = operationId + " " + path; if (method === "DELETE" || DESTRUCTIVE_HINTS.test(hay)) return "high"; if (method === "GET" || method === "HEAD" || method === "OPTIONS") return "low"; return "medium"; }

You review and can override every classification in the console before a tool ever goes live — this is the point where you catch, for instance, an operation named archiveInvoice that's actually irreversible in your system even though nothing in its name says "delete."

How risk becomes a decision

Risk level isn't just a label — it drives what the runtime does on every single call, via the policy decision point (PDP). Each invocation resolves to exactly one of four outcomes:

  • allow — read-only, within scope and rate limits. No friction.
  • allow_with_confirmation — financial or side-effecting. The agent (or its user) confirms before it executes.
  • allow_with_approval — destructive. The call is held for a designated human approver, with full context, before it runs at all.
  • denied — missing scope or over a rate limit. Never reaches your downstream API.

This is exactly why the risk classification matters beyond bookkeeping: a high-risk delete_message tool sitting in an otherwise public, low-friction server still can't be fired off by an agent unsupervised — it queues in the approvals view until a human looks at the arguments and the requester and makes a call.

Putting it into practice: exposing an internal API safely

Say you're wiring up an internal admin API — user management, billing adjustments, the works — for an internal support-copilot agent. The combination that keeps this safe:

  1. Create the server with boundary internal so it's never reachable outside your org, regardless of how any individual tool is scoped.
  2. Generate tools from the OpenAPI spec and let the pipeline's default classification stand for anything genuinely read-only (list users, get account status) — those get allow and the agent can use them freely.
  3. Check every write and delete operation by hand. A billing-adjustment tool might come back medium by default (it's a POST, not a DELETE) but you may want to bump it to high if a mistake there is costly — forcing allow_with_approval instead of a bare confirmation.
  4. Publish, connect the agent, and watch the audit log and approvals queue as real usage comes in — both boundary and risk decisions are visible per call, not just per tool definition.

The result: the agent gets broad, low-friction access to the parts of your internal API that are genuinely safe to automate, and every genuinely dangerous action still needs a human in the loop — without you having to write that logic yourself.

None of this — boundaries, risk levels, or the approval queue — exists if you write your own MCP server by hand. See MCP Platform vs. DIY for the full picture.