跳到主要內容

ARC developer documentation

Access tiers

A request passes four independent gates: the method allowlist, the credential, the path policy, and the provider's capabilities. A credential clears one of them, not all four.

Whether a call succeeds is decided by four gates, in this order. They are independent: clearing one says nothing about the next.

The most common mistake is assuming a credential is enough. It clears gate 2. Gates 3 and 4 still apply, and on a blocklet that has declared no network rules they will still refuse a write from an owner-role caller.

#GateDecided byFailure looks like
1Method allowlistWhether the JSON-RPC method is anonymous-safe401 + WWW-Authenticate
2CredentialThe bearer token or session cookie you sent401 + WWW-Authenticate
3Path policyWhat the blocklet declared for that path200 with isError and AFS_FORBIDDEN
4Provider capabilityWhat the provider behind the path implements200 with isError and a not-supported error

Gates 1 and 2 are transport-level: they answer with an HTTP status and no JSON-RPC result. Gates 3 and 4 are inside a successful MCP call: the call itself worked, the operation did not. See Errors.

Why there are four

Because three different parties decide them, and none can answer for another.

GateDecided byWhat it is really asking
1, 2The runtimeIs this method safe without a caller, and is this caller who they say they are?
3The blockletHave I opened this path to the network?
4The providerDo I implement this operation at all?

This is why a credential is not enough, and why no better credential would help. A credential is an answer to the runtime, and the runtime does not own gates 3 and 4. No token can speak for the blocklet, and none can add an operation the provider never implemented.

It also explains the failure shapes. Gates 1 and 2 are decided before a tool is reached, so they can only answer in HTTP. Gates 3 and 4 are decided by code that already ran, so they answer inside a 200.

Gate 1: the method allowlist

Without a credential, only these are allowed:

Allowed anonymouslyNot allowed anonymously
initialize, notifications/initializedtools/call with afs_write, afs_delete, afs_exec
tools/list, resources/list, prompts/listAny other method, including unknown ones
tools/call with afs_read, afs_list, afs_search, afs_stat, afs_explain, search_content, list_content, get_content

The rule is fail-closed: anything not on the list is refused, and in a batch every message must be allowed or the whole batch is refused.

Gate 2: the credential

Three ways to clear it:

TierCredentialWhere it comes from
Anonymousnonen/a
AuthenticatedAuthorization: Bearer blocklet-… or a DID-Connect session cookieAuthorize a client or the device grant
Loopback operatorverified same-machine socket peer, Node runtime onlyNothing to obtain. Never available over the network; the Cloudflare runtime never grants it

Roles

When a person approves a client, the consent screen asks for an access level. Four are offered:

Role
ownerthe default on the consent screen
admin
member
guest

The credential carries the role the person picked, scoped to that blocklet instance. It is not a global role: the same person can be owner on one blocklet and have no access on another.

The role is an upper bound, not a grant. It cannot open a path that gate 3 has closed.

Gate 3: the path policy

This is the gate most callers hit and the one no credential can bypass.

A blocklet decides, per path, what a network client may do. Paths that were not declared are closed to the network, including to an owner. The refusal is explicit about the fix:

json
{
  "result": {
    "content": [{
      "type": "text",
      "text": "AFS_FORBIDDEN: Forbidden at /instance/notes.txt: network clients cannot write base paths; use a resolver overlay or internal code"
    }],
    "isError": true
  },
  "jsonrpc": "2.0",
  "id": 3
}

Read and list produce the same shape with cannot read base paths / cannot list base paths and declare a networkRead rule or use an overlay.

What the declaration changes, compared across two blocklets:

CallBlocklet that declares nothingBlocklet that declares collections with readRole: guest
anonymous afs_list /refused200
anonymous afs_list /packagesrefused200
anonymous afs_list /.knowledgerefusedrefused, because that path was not declared either
anonymous list_content on a declared collectiontool not registered200
owner-credential afs_write on any base pathrefusedrefused

The last row is the one to remember. Writing from the network is not something a credential turns on; it needs a resolver overlay or code running inside the blocklet. If you are building an agent that must write, that is a decision for whoever owns the blocklet. See Declare what agents see.

Gate 4: provider capabilities

Each path is served by a provider that implements only some operations. afs_stat reports both what the provider can do and what you are allowed to do:

json
{
  "capabilities": ["list", "read", "stat", "search", "write", "delete", "exec", "explain"],
  "accessMode": "readonly"
}

capabilities is the provider's implementation. accessMode is your effective access after gate 3. A path can list write in capabilities and still be readonly for you.

Check capabilities before making an operation part of an integration. See AFS for the contract these operations belong to.

What this means in practice

  • Reading published content works without any credential when the blocklet declared it. That is the intended path for most agents, and it is what the content tools are for.
  • Getting a credential is worth doing when you need a specific person's view of the data, or when the blocklet has opened write paths to the network.
  • A credential is not a write switch. If writes are refused after you authenticate, gate 3 is the reason and the blocklet owner is the only one who can change it.