# auth.md

You are an agent. Adnanna gives every brand a team works with its own agent: its ads, its competitors' ads, Nana's reads of every video, its kit and its memory. You can reach them over MCP (https://adnanna.com/mcp for every brand, https://adnanna.com/mcp/<agent> for one) and the REST API (/v1). This file tells you how to get your own key, with a person's approval, without anyone copying one to you.

Hosts: everything is on https://adnanna.com. The API (/v1) is at the `api_url` the token response gives you.

## Step 1 — Discover

A 401 from https://adnanna.com/mcp carries `WWW-Authenticate: Bearer resource_metadata="https://adnanna.com/.well-known/oauth-protected-resource"`. Fetch it, then the authorization server metadata:

```http
GET https://adnanna.com/.well-known/oauth-protected-resource
GET https://adnanna.com/.well-known/oauth-authorization-server
```

The second has `token_endpoint`, `device_authorization_endpoint`, `revocation_endpoint`, and the `agent_auth` block (`identity_endpoint`, `claim_endpoint`, `identity_types_supported: ["service_auth"]`).

## Step 2 — Pick a method

1. **You know the person's email** → [service_auth](#service_auth) (auth.md). Only that person can approve.
2. **You don't** → [device authorization](#device-authorization) (RFC 8628). Whoever signs in with the code approves.

`anonymous` and `identity_assertion` (ID-JAG) aren't offered: they return `anonymous_not_enabled` / `issuer_not_enabled`.

Scopes: `mcp` (an MCP key: every brand of the team, or one brand, as the person chooses) and `api` (an API key for /v1). Ask for `mcp api` to get both.

## Step 3 — Register

### service_auth

```http
POST https://adnanna.com/agent/identity
Content-Type: application/json

{ "type": "service_auth", "login_hint": "person@agency.com", "client_name": "Claude Code", "scope": "mcp api" }
```

Response:

```json
{
  "registration_id": "reg_…",
  "registration_type": "service_auth",
  "claim_url": "https://adnanna.com/agent/identity/claim",
  "claim_token": "clm_…",
  "claim_token_expires": "…",
  "post_claim_scopes": ["mcp", "api"],
  "claim": { "user_code": "123456", "expires_in": 600, "verification_uri": "https://adnanna.com/device?attempt=…", "interval": 5 }
}
```

Keep `claim_token` in memory only. If the code window passes, `POST https://adnanna.com/agent/identity/claim` with `{ "claim_token": "…", "email": "person@agency.com" }` for a fresh `claim_attempt` block.

### device authorization

```http
POST https://adnanna.com/oauth2/device_authorization
Content-Type: application/x-www-form-urlencoded

client_id=Claude%20Code&scope=mcp%20api
```

Response:

```json
{ "device_code": "dc_…", "user_code": "WDJB-MJHT", "verification_uri": "https://adnanna.com/device", "verification_uri_complete": "https://adnanna.com/device?code=WDJB-MJHT", "expires_in": 600, "interval": 5 }
```

## Step 4 — Hand off to the person

Show the link and the code in one message:

> Open https://adnanna.com/device…, sign in, and enter **123456** (the code goes into that page, not back to me).

They sign in with Google, check the code, choose whether you may reach every brand or one, and approve.

## Step 5 — Poll for the key

```http
POST https://adnanna.com/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=clm_…
```

or, for the device flow, `grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=dc_…`.

While waiting: `{ "error": "authorization_pending" }` (or `slow_down`: add 5 seconds). Then:

```json
{
  "access_token": "adn_mcp_…",
  "token_type": "Bearer",
  "scope": "mcp api",
  "mcp_url": "https://adnanna.com/mcp",
  "api_key": "cj_live_…",
  "api_url": "https://…"
}
```

`access_token` is an MCP key for `mcp_url`; `api_key` is for the REST API (when only `api` was granted, `access_token` is the API key). It's handed over once and doesn't expire: store it like a password.

## Step 6 — Use it

```bash
claude mcp add --transport http adnanna https://adnanna.com/mcp --header "Authorization: Bearer adn_mcp_…"
```

On the team server, start with `list_agents`; every tool then takes `agent` (a brand's name or id). On one brand's server, start with `client_overview`.

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| `anonymous_not_enabled`, `issuer_not_enabled` | /agent/identity | Use service_auth. |
| `invalid_request` | /agent/identity | Send `type: "service_auth"` and a real email as `login_hint`. |
| `invalid_claim_token`, `claim_expired`, `claimed_or_in_flight` | /agent/identity/claim | Start again at Step 3. |
| `authorization_pending` | /oauth2/token | Keep polling every `interval` seconds. |
| `slow_down` | /oauth2/token | Add 5 seconds to your interval. |
| `expired_token` | /oauth2/token | For service_auth, get a fresh code at /agent/identity/claim; for the device flow, start again. |
| `access_denied` | /oauth2/token | The person declined. Don't retry without asking them. |
| `invalid_grant` | /oauth2/token | Already exchanged or unknown: start again. |

## Revocation

`POST https://adnanna.com/oauth2/revoke` with `token=<key>` (form-encoded) kills a key; always 200. People can also revoke keys in the app (an agent's Connect MCP dialog, or Keys and API).
