Safety & side-effects
Every Geopera operation declares a side-effect tier, and each MCP tool carries standard MCP tool annotations derived from that tier so an agent can reason about a call’s blast radius before it makes it.
Where the hints come from
Each operation declares a side_effect, one of five tiers (see Core concepts for the full pipeline view):
| Tier | Meaning |
|---|---|
read | Returns data; changes nothing. |
compute | Runs work (analytics, processing) but mints no external charge. |
external_spend | Moves real money or credits with a third party. |
share_export | Issues a share link or export, which may reveal a one-time secret. |
destructive | Mutates or deletes durable state. |
Each tool’s MCP ToolAnnotations are stamped from its tier by a single mechanical rule, applied to every tool with no per-operation hand-tuning:
| Tier | readOnlyHint | destructiveHint | idempotentHint | openWorldHint |
|---|---|---|---|---|
read | true | false | false | true |
compute | false | false | false | false |
external_spend | false | false | true | true |
share_export | false | false | false | true |
destructive | false | true | false | false |
Because the annotations follow directly from the tier, they stay consistent: add an operation with a given tier and its tool is annotated correctly.
What each hint means here
readOnlyHintistrueonly forreadoperations. Atruevalue is the agent’s signal that the tool is safe to call freely — it cannot change state.compute,external_spend,share_export, anddestructivetools all reportreadOnlyHint: false.destructiveHintistrueonly fordestructiveoperations — those that mutate or delete durable state (cancelling, archiving, deleting). Treat these as not reversible by simply calling the tool again.idempotentHintistrueonly forexternal_spendoperations. These carry anIdempotency-Keycontract on their route, so a repeat with the same key is side-effect-free (it returns the original result instead of charging twice). See Idempotency.openWorldHintistrueforread,external_spend, andshare_export— the tiers whose result depends on or affects systems outside your own account state (live catalog data, a payment processor, an externally reachable share URL).computeanddestructiveoperate on the closed world of your own account state and reportopenWorldHint: false.
Note that compute and share_export tools carry no true mutation hint of their own: compute is readOnlyHint: false with every other hint false, and share_export is flagged only via openWorldHint. The absence of destructiveHint/idempotentHint is itself information — see the gotchas below.
Structured metadata
Beyond the boolean hints, each tool’s meta carries the raw tier and governance facts so a client can filter or route on them without parsing the description text:
{
"x-side-effect": "external_spend",
"x-required-scope": "orders:write",
"x-produces": ["order"]
}Use x-side-effect for precise gating (it distinguishes all five tiers, where the boolean hints collapse compute and share_export), and x-required-scope to predict whether a call will pass scope authorization at all.
external_spend: money moves, and idempotency protects it
external_spend tools are the ones that cost real money or credits — placing an order, topping up a balance, dispatching paid processing. They are annotated idempotentHint: true because their route honours the Idempotency-Key contract.
What that means for an agent:
- Generate one key per intended charge. Before calling a spend tool, mint a fresh idempotency key (a UUID is fine) and reuse it on retries for that same intended action.
- Retrying with the same key is safe. If a network blip or timeout leaves you unsure whether the charge landed, replaying with the same key returns the original result rather than charging again.
- A new key means a new charge. Generating a fresh key for a retry defeats the protection and can double-spend.
How the key reaches Geopera depends on your MCP client. If your client can attach request headers to a tool call, send Idempotency-Key. If it can only pass tool arguments, supply the key in the operation’s input where the operation accepts one. Full mechanics, including the response semantics on a replay, are on the Idempotency page — do not re-derive them.
share_export: a secret you may see only once
share_export operations (creating a share link, issuing an export) can return a credential — a signed URL or token — that is shown once in the tool result and is not retrievable afterward. For an agent this has two consequences:
- Capture the result immediately. If the returned URL or token is discarded, re-running the tool typically mints a new secret rather than returning the old one; the first is then unrecoverable.
- Treat the result as sensitive. A share URL is a bearer capability. Don’t echo it into logs, transcripts, or downstream prompts that you wouldn’t trust with the underlying data.
These tools are flagged openWorldHint: true (the resulting URL is reachable from outside) but carry no destructive hint, so a naive “only confirm destructive tools” policy will miss them. Gate them on x-side-effect == "share_export" explicitly.
destructive: mutate or delete
destructive tools change or remove durable state and are annotated destructiveHint: true. They do not carry the idempotency contract that spend tools do, so re-calling one is a fresh mutation, not a safe replay. Surface these for confirmation and avoid speculative retries.
Recommended gating pattern for agents
The hints describe risk; they do not enforce it. Enforcement still happens server-side — an agent can never exceed its token’s scopes, and a payment problem comes back as a 402 problem+json the agent can reason about. But a well-behaved agent should also gate at the client, before the call, using the annotations:
- Free-call the safe tier.
readOnlyHint: true(thereadtier) → call without prompting. - Confirm before spending.
idempotentHint: true/x-side-effect == "external_spend"→ pause for explicit user confirmation. Where possible, call the matching*.estimateread tool first to show the cost, then place the charge with a stable idempotency key. - Confirm before destroying.
destructiveHint: true→ pause for confirmation; never auto-retry on ambiguity. - Confirm before sharing.
x-side-effect == "share_export"→ pause for confirmation and handle the returned secret as one-time and sensitive. - Let
computerun, within reason.computeis not free of cost in time/resources but mints no external charge; gate on a budget or quota policy rather than per-call confirmation if you choose to.
A concrete confirmation flow for a spend tool, estimate-then-place:
Agent: catalog/items search → read, no prompt
Agent: orders.archive.estimate → read, no prompt (shows price: 240 credits)
Agent: "Place this order for 240 credits? (y/n)" ← confirmation gate
User: y
Agent: orders.archive.place → external_spend
with Idempotency-Key: 6f1c… (reused verbatim on any retry)Pseudocode for a gate keyed off the annotations and metadata your client receives per tool:
type SideEffect = 'read' | 'compute' | 'external_spend' | 'share_export' | 'destructive';
function needsConfirmation(tool: {
annotations: { readOnlyHint?: boolean };
meta: { 'x-side-effect'?: SideEffect };
}): boolean {
if (tool.annotations.readOnlyHint) return false; // read → safe
const tier = tool.meta['x-side-effect'];
return (
tier === 'external_spend' || // money
tier === 'destructive' || // mutate/delete
tier === 'share_export'
); // one-time secret
// compute falls through to false here — gate it on budget/quota instead.
}Gotchas
- The boolean hints collapse two tiers.
computeandshare_exportboth reportfalsefordestructiveHintandidempotentHint. If your policy only inspects the booleans, it will treat ashare_exporttool as “harmless.” Always consultx-side-effectinmetafor the precise tier. idempotentHintis not a license to retry blindly. It is only meaningful when you send the sameIdempotency-Key. A retry with a new key is a new charge.destructivetools are not idempotent. They lack the spend tier’s replay contract; a repeated call mutates again.- Annotations are advisory, scopes are enforced. A hint never blocks a call. The scope check does — an agent calling a tool it lacks the scope for is rejected regardless of what its policy decided.
- Read and list tools are
readOnlyHint: true— call them freely. The surface now includes the read/list operations an agent uses to read back what it creates (list orders, fetch a collection, list notifications, check a balance or usage, poll a job list). They reportreadOnlyHint: true; they change nothing and need no confirmation gate. - Some operations have no tool at all. A few categories are excluded by the gateway and carry no annotation to reason about: raster tiles and rendered images (frontend map plumbing), binary downloads such as exports, reports, and clip downloads (large egress, better served by a signed URL), streaming NDJSON/SSE listings (a single-result tool can’t carry a stream — use the paged sibling), and admin/cron operations plus a small set of untyped destructive or external-spend mutations (withheld for safety). The governed, typed mutations you need stay available with their hints intact. See Tool reference for the full breakdown.
Related
- Core concepts — the side-effect tiers and the invoke pipeline they belong to.
- Idempotency — the
Idempotency-Keycontract that backsidempotentHint. - Scopes — what actually enforces authorization on every tool call.
- Errors — the problem+json shape an agent gets back when a call is rejected.