Sume MCP tools_schema safety object: build a tool allowlist from it
Sume's tools_schema returns a safety object per tool: paid_generation, read_only, requires_idempotency_key and more. Build your agent allowlist from it.

Each Sume MCP tool has a safety object with seven fields: paid_generation, read_only, requires_allow_paid, requires_allow_write, requires_idempotency_key, requires_max_spend_usd and returns_sensitive_url. Read it from tools_schema or tools_list, and build the allowlist for an unattended agent from those fields rather than from tool names.
What each field tells you
The values for paid creates are specific. A paid create has paid_generation: true, requires_idempotency_key: true and returns_sensitive_url: true. It has requires_allow_paid: false and requires_max_spend_usd: false, because the legacy allow flags are accepted but not required and the cap is optional. Two dev-only analysis tools do require max_spend_usd.
| Field | Meaning for your harness |
|---|---|
| read_only | safe for a read-only OAuth session |
| paid_generation | spends credits; needs a cap and a key |
| requires_idempotency_key | you must send a stable key |
| requires_max_spend_usd | the call is refused without a cap |
| returns_sensitive_url | the result carries a signed or private URL; do not echo it |
| requires_allow_paid / requires_allow_write | legacy flags; cannot bypass scope |
A filter
Given the parsed tool list, split tools into what an unattended agent may call.
def allowlist(tools: list[dict], allow_paid: bool = False) -> list[str]:
keep = []
for t in tools:
s = t.get('safety') or {}
if s.get('read_only'):
keep.append(t['name'])
elif allow_paid and s.get('paid_generation'):
keep.append(t['name'])
return keep
sample = [
{'name': 'balance_get', 'safety': {'read_only': True}},
{'name': 'tts_create', 'safety': {'paid_generation': True}},
]
print(allowlist(sample), allowlist(sample, True))Limits
With a progressive credential, tools_list returns compact rows with read_only and paid flags, so fetch one tool's full contract with tools_schema before relying on the seven fields. The allowlist is a client-side convenience. The server still enforces scopes: a hidden tool answers insufficient_scope with required_scope: mcp:write. A tool name that does not exist answers tool_not_found, with a did-you-mean suggestion.
Treat the filter as defence in depth. The strongest control for an unattended run is still the credential itself: a read-only OAuth session cannot call write or paid tools at all, whatever your allowlist says.
Annotations are not the whole story
Protocol-level annotations such as readOnlyHint and idempotentHint reach every client as part of the tool list. The richer safety object does not travel there; it comes from tools_schema and tools_list, and each tool result repeats it under agent.safety. A harness that wants to build an allowlist should read safety, since annotations alone cannot tell a paid create from a cheap write.
Checklist before you ship
- Allowlist tools by read_only true for unattended agents with no write budget.
- Require requires_idempotency_key true tools to carry a derived key.
- Hide or confirm any tool with returns_sensitive_url true.
- Re-read the contract after a release instead of caching it for weeks.
Sources
Related posts
More in Developers
- Why a Sume output schema is rejected: the strict subset rules
Sume saves an output schema only in the strict subset: object root, additionalProperties false, all properties required, nullable unions, 10 levels.
- Sume queue position and ETA: none exists, poll generation_limits
Sume shows queue counts and remaining capacity, not a per-job position or ETA. Read generation_limits and keep new work inside the headroom formula.
- Sume schedule invoke command: check the host before you paste it
The curl command on a Sume schedule's trigger card uses api.dev.sume.com when you copy it from a *.dev.sume.com dashboard. Check the host before production.
- Sume schedule run input limits: 64 properties and 2 MiB
The input object on a Sume schedule or Agent Completion run takes up to 64 properties and 2 MiB of UTF-8. Where it lands and how to keep large data out of it.
Written by Sume