メインコンテンツへスキップ

ARC developer documentation

Authorize a client

Follow the challenge from a rejected call to the authorization server, register the client, obtain a credential, and use it, plus what the credential leaves closed.

A client that receives 401 with a WWW-Authenticate challenge has everything it needs to reach the authorization server. Every step is a plain GET or POST against the same host. Replace <host> with the blocklet's host.

StepRequestResult
1tools/call a write tool, no credential401 + WWW-Authenticate carrying the metadata URL
2GET /.well-known/oauth-protected-resourceWhich authorization server protects this resource
3GET /.well-known/oauth-authorization-serverEndpoints, grant types, PKCE method
4POST /.well-known/service/oauth/registerA client_id for this client
5Authorize, then exchange for a credentialAuthorization: Bearer blocklet-…

Step 2: protected resource

bash
curl -s https://<host>/.well-known/oauth-protected-resource
json
{
  "resource": "https://<host>/mcp",
  "authorization_servers": ["https://<host>"]
}

The authorization server is the same host. A blocklet protects only its own resource and issues only its own credentials, so a client that talks to several blocklets registers and authorizes with each one separately.

Step 3: authorization server metadata

bash
curl -s https://<host>/.well-known/oauth-authorization-server
json
{
  "issuer": "https://<host>",
  "authorization_endpoint": "https://<host>/.well-known/service/oauth/authorize",
  "registration_endpoint": "https://<host>/.well-known/service/oauth/register",
  "device_authorization_endpoint": "https://<host>/.well-known/service/oauth/device_authorization",
  "token_endpoint": "https://<host>/.well-known/service/oauth/token",
  "response_types_supported": ["code"],
  "grant_types_supported": [
    "urn:ietf:params:oauth:grant-type:device_code",
    "authorization_code",
    "refresh_token"
  ],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "scopes_supported": ["mcp"]
}

token_endpoint_auth_methods_supported is none, so a client authenticates the token request with its PKCE verifier rather than a client secret. scopes_supported has one entry, so there is no narrower or broader scope to choose between.

Step 4: register the client

A client obtains its client_id by registering itself. No pre-shared identifier is handed out.

bash
curl -s -X POST https://<host>/.well-known/service/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "my agent",
    "redirect_uris": ["http://127.0.0.1:3118/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "none"
  }'
json
{
  "client_id": "b665f46e-8fb5-4bf9-bd32-53df5b35f7f5",
  "redirect_uris": ["http://127.0.0.1:3118/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

The response echoes the registration, not just the identifier. Clients validate this and reject a reply that omits fields they sent.

Register once and keep the client_id. Registration is rate limited per source address, and a registration never used in a completed authorization is swept after seven days.

Redirect URIs

A client running on the user's machine listens on a port chosen at run time, so register the exact redirect_uris this session will use rather than expecting a fixed value to be pre-approved. Loopback addresses, https URLs, and private-use schemes are accepted; anything else is rejected. See Errors.

Step 5: obtain the credential

Two grants lead to the same credential. Your tool usually picks one for you, see Connect your tool.

Your clientGrant
Can open a browser and listen on a local port, as most MCP clients doauthorization_code with PKCE, using authorization_endpoint then token_endpoint
Cannot: a CLI, CI job, or headless agentThe device grant, see Credentials without a browser

Both end at the same token_endpoint and return the same credential shape.

What the credential is

The token endpoint returns a bearer token that is also an access key:

json
{
  "access_token": "blocklet-zEJYCdC9awCqxEPLhqreFbwXjqU6Y2BHvKVQAx6kMZfRh",
  "token_type": "Bearer",
  "scope": "mcp"
}

Three things follow from that response, and each matters:

  • The blocklet- prefix is how the runtime recognises it. OAuth token and access key are not two mechanisms; they are one credential with one prefix.
  • It carries the role the person picked on the consent screen (owner, admin, member, or guest), scoped to this blocklet instance. See Access tiers.
  • The device grant returns no expires_in and no refresh_token. The authorization server advertises refresh_token as a supported grant, but the device flow does not hand one back.

Use it

Send it as a bearer token on /mcp:

bash
curl -s -X POST https://<host>/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Authorization: Bearer blocklet-…' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The 401 is gone: the same tools/call that was refused before now reaches the tool.

What the credential leaves closed

Reaching the tool is not the same as the operation succeeding. An authenticated write can still come back like this, 200 at the transport layer but refused inside:

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
}

An owner-role credential gets the same refusal. Writing from the network is not something a credential turns on. The blocklet must have declared that path open to network clients, via a resolver overlay or code running inside it.

If your agent must write, that is a decision for whoever owns the blocklet. Read Access tiers before designing around it.