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.

4 min readSume
All posts

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.

Safety fields on Sume MCP tools (read 2026-10-05 against the Sume codebase)
FieldMeaning for your harness
read_onlysafe for a read-only OAuth session
paid_generationspends credits; needs a cap and a key
requires_idempotency_keyyou must send a stable key
requires_max_spend_usdthe call is refused without a cap
returns_sensitive_urlthe result carries a signed or private URL; do not echo it
requires_allow_paid / requires_allow_writelegacy 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

All Developers posts

Written by Sume