AI agents
Read as MarkdownScope recipes for agent jobs
The minimum scope set for each common agent job, what each scope buys, and what to add when a job needs more.
Every MCP tool is gated twice, and both gates must pass:
- the umbrella for that side of the endpoint —
mcp:readfor every read,mcp:writefor every mutation; - the domain scope in that tool’s own row of the MCP tool reference.
A key holding an umbrella and nothing else authenticates and is served an empty tool list. That is the single most common “the server does not support this” report, and it is a scope problem.
Start from a bundle
anectico apikey create --scope-profile <name> and anectico agent bootstrap --scope-profile <name>
materialize a bundle into explicit scopes, stored on the key. The key records the scopes, never
the bundle name, so its authority is fixed at mint time and is auditable by reading it back with
anectico apikey list -o json.
| Bundle | Scopes | Tools the key is served | Mintable by |
|---|---|---|---|
investigate |
20 | 36 — read-only, no mutation at all | any member |
incident |
38 | 49 | owner or admin |
agent-observability |
10 | 14 | owner or admin |
admin |
2 (the two umbrellas) | 0 | owner or admin |
Three properties of that table are decisions rather than accidents:
incidentcontainsinvestigatein full. Incident response is investigation plus a lifecycle surface, not a different way of looking at the same data.agent-observabilitydoes not. It is deliberately narrow — agent runs, their transcripts, what they cost, and the person behind them. Add--scope traces:readwhen the raw spans behind a run are needed.adminrefuses to stand alone. It buys the two umbrellas and a statement of intent; the authority is typed out one--scopeat a time.anectico apikey create --scope-profile adminwith no--scopeis a usage error, and a key holding only the umbrellas is served nothing.
No bundle ever materializes a *:delete scope. Deletion is the one class of write that cannot be
undone, and the preview-then-confirm gate that makes it survivable lives in the MCP write framework —
a key used against the REST API gets no such gate. Every delete scope must be named explicitly.
Two things about who can mint. A key can never carry a scope its creator lacks — the mint is
refused by name, so incident, agent-observability and admin are owner-or-admin work because all
three carry mcp:write. And a viewer cannot mint a key at all, of any shape: key creation is
itself a privileged act, and the refusal comes before the scope list is read, so hunting for the
missing scope is a dead end.
Recipes
Each row is the minimum that makes the job work end to end. Every one also needs the umbrella already shown.
These recipes assume a project-bound key — what anectico agent bootstrap mints unless you pass
--org-wide. A project-bound key carries its project signed into the credential; it supplies that
project on every call, and an explicit different one is denied. It therefore needs no
projects:read: there is no project for it to discover.
An organization-wide key adds exactly one scope: projects:read. Without a binding, it must pass
a project id to every project-addressed read and write, and list_projects is the only way an agent
finds one. That is the whole difference — everything else in each row is the same. Counts below are
for the project-bound form, derived from the current capability catalog and registration rules on
2026-09-04. Server tests pin the published bundle and served-tool counts, but catalog or profile
changes can alter them; verify the surface served to your own key before automating against a count.
Read-only jobs (mcp:read)
| Job | Add these scopes | Tools |
|---|---|---|
| Triage an error issue | errors:read logs:read traces:read persons:read services:read |
14 |
| Answer “what happened to this customer” | persons:read persons:profile:read errors:read logs:read traces:read analytics:read replay:read replay:content:read groups:read agents:read |
28 |
| Watch AI agents and their spend | agents:read llm:read |
4 |
| Everything an investigation needs | the investigate bundle |
36 |
The triage row is the smallest genuinely useful read key: it gets search, fetch,
list_issues, get_issue, get_affected_users, search_logs, get_log_patterns,
search_traces, get_trace, get_person_timeline, resolve_person, search_persons,
search_issues and get_service_health. Drop persons:read and it falls to 11: no person
timeline, no identity resolution, and search stops having a people source to search.
The agent-watch row looks too small, and is not. Four tools is right: run metadata is
list_agent_runs, fleet posture is list_agent_fleet, and everything else this key can do is a read
action reached through list_read_actions and execute_read_action, which is why none of it adds
an entry to the tool list. Behind those two doors the same key gets all three LLM-cost capabilities
— the summary, the series and the top spenders — plus list_agent_sessions (which conversations
happened), list_agent_turns (the exchanges within one of them), get_agent_run_graph (one run’s
step structure), list_context_events (the context assembly evidence),
get_telemetry_completeness (how current the agent telemetry itself is), and
verify_receipt_chain (whether the agent’s action receipts still verify). Two scopes are commonly
added to the row, and each should be a decision rather than a reflex:
| Add | What it buys | What it also exposes |
|---|---|---|
agents:content:read |
get_agent_run — one run’s detail |
The verbatim transcript: the prompts, completions and tool messages the model exchanged. If the agent under observation handled customer data, this is that data |
persons:read |
get_person_timeline, resolve_person, search_persons — tying a run or a bill to the person behind it |
The people surface generally, and a search that now has a person source |
Neither is required to watch runs or to read spend. Add them when correlation to a person, or reading what a run actually said, is the job.
Jobs that change something (mcp:read + mcp:write)
| Job | Add these scopes | What it unlocks |
|---|---|---|
| Triage and resolve issues | errors:read errors:write logs:read traces:read persons:read |
set_issue_status, snooze_issue, merge_issues, unmerge_issue |
| Take a page and run the response | alerts:read alerts:acknowledge alerts:resolve incidents:read incidents:respond channels:read oncall:read members:read |
acknowledge_alert, resolve_alert, comment_incident, set_incident_status, plus delivery receipts and who is on call |
| Silence a storm | silences:read silences:write silences:delete |
create_silence, delete_silence |
| File a ticket from an issue | connections:read errors:read tickets:read tickets:write |
create_ticket, list_tickets_for_issue, get_ticket. Add tickets:delete for unlink_ticket |
| Build and maintain dashboards | dashboard:read dashboard:write dashboard:delete |
the seven dashboard and widget actions. dashboard:delete covers delete_dashboard and delete_widget |
| Export data for offline analysis | export:read export:write export:download plus the read scope of every signal exported (logs:read, traces:read, …) |
create_export_job, export_logs, cancel_export_job, retry_export_job, and the three export reads |
| Delete exact telemetry on request | logs:delete and/or traces:delete — project-bound only |
delete_log, delete_trace. An org-wide key is not served these at all |
Export is the row where a key most often “sees a tool it cannot call”, and it is deliberate.
export:write alone commissions nothing: creating a job also requires the exported signal’s own read
scope, so a key cannot export data it may not read. retry_export_job re-checks that against the
stored job, so a key that has since lost the read scope cannot regenerate the file. And
get_export_download re-checks it and requires being the job’s creator or an organization owner
or admin — so a key with only export:write can create a job and can neither poll it nor download
it, and a key that did not create a job is answered as though the job does not exist.
What to add, and why
projects:read, only for an organization-wide key. It buys exactly one capability,
list_projects, which is how an unbound agent discovers the project ids the rest of the surface
takes as an argument — without it such a key fails on the first call an agent makes. A project-bound
key gains nothing from it: adding projects:read to the customer recipe above changes the tools
served from 28 to 28. It is in the investigate bundle because that bundle has to work either way.
The read that matches the write. A write scope without its matching read scope is what turns an
ordinary edit into an unrecoverable one: the credential cannot capture what it is about to
overwrite, so the preview can only tell you it is unable to show you — and the action is graded
destructive for precisely that reason. escalation:write without escalation:read cannot list the
policy it is about to replace. evals:write without evals:read can replace a sampling rule
or a budget it never saw, and setting a rate to zero turns automatic evaluation off. If an agent should be able to
undo its own edits, give it the read.
A content scope, only when verbatim customer content is genuinely needed. persons:profile:read
(profile properties), replay:content:read (session playback and its console and network payloads)
and agents:content:read (prompts, completions, tool arguments, reasoning transcripts) each name
recorded content rather than a summary of it. The investigate bundle grants all three, because
that detail is what makes an investigation possible — if an agent should not see it, name scopes
individually instead of taking the bundle. traces:read, logs:read and metrics:read used to be
second routes to model prompts and completions recorded as attributes by third-party
instrumentation, and are not any more: those attributes are withheld from a credential without
agents:content:read — on spans, on log records (including the live tail) and on metric exemplars —
and the response comes back carrying an anectico.content.withheld attribute naming what was
removed. An agent with the signal read scopes and no content scope reads latency, status, token
counts and log messages, never model bodies.
Your project’s content policy can withhold the same bodies from an agent that does hold
agents:content:read. Those responses carry anectico.content.policy_withheld instead, and the
difference matters to whoever is debugging: the first is fixed by minting a key with the content
scope, the second only by changing the project’s policy. One more thing to know before you blame a
key: a content policy belongs to a project, so a read that does not name one — the MCP trace and log
searches on an organization-scoped key, or an API call without project_id — comes back without
content for that reason alone. Name the project and the bodies return.
A delete scope, one at a time and never by reflex. --scope logs:delete, --scope traces:delete, --scope dashboard:delete, and so on. Typing it out is the point.
Nothing for the organization itself unless the job is administration. members:write,
projects:write, settings:write, connections:write and api_key:write all belong to the
admin privilege class. A key that investigates does not need them, and the wildcard platform-admin
permission is not grantable to any key.
Narrow a session without minting a key
Adding ?profile=<name> to the endpoint URL narrows one connection. It takes tools away for that
whole connection — an excluded tool is not listed and cannot be called — but it can only ever
subtract from what the key’s scopes already permit, and the key’s authority is unchanged.
The useful case is turning a write-capable key into a read-only session:
https://app.anectico.com/mcp?profile=investigate
investigate declares no mutation at all, so a write-capable key connecting that way is served no
write gateway whatsoever — an incident key drops from 49 tools to 35, and cannot invoke a mutation
for the life of that connection. Reconnecting without the parameter restores everything, which is
why this is a focus control and not an authorization boundary: the caller chooses it.
Watch for the side effect. investigate also excludes reads that are native to other bundles, so an
incident key connected this way loses get_incident, list_incidents and the on-call and
delivery reads too, despite holding their scopes. For a genuinely narrower authority, mint a
narrower key; see How agent access is enforced for exactly where that
line falls.
- Permission scope reference — the complete scope catalog.
- MCP tool reference — every tool with the scopes it requires.
- How agent access is enforced