# auth.md — Muriel AI

How an AI agent gets credentials for Muriel (https://murielai.io): US drinking water compliance data —
published sample results, notable events, trends — and the federal drinking water rules.

Agents always act **for a person** who has (or creates) a Muriel account. Access is
read-only (scope `muriel.read`). Tool calls use the person's credits on the same price
list as the app and the API -- 1 credit per 1,000 records returned, added up across
calls; a call that returns nothing is free; out of credits is HTTP 402 -- and each
account is limited to 30 calls a minute and 2,000 a day.

## 1. Discover

- Protected resource metadata: `https://murielai.io/.well-known/oauth-protected-resource`
  and, for the MCP server, `https://mcp.murielai.io/.well-known/oauth-protected-resource/mcp`
- Authorization server metadata: `https://murielai.io/.well-known/oauth-authorization-server`
  (issuer `https://murielai.io`). Its `agent_auth` block points back here.

## 2. Pick a method

| You are… | Use |
|---|---|
| An MCP client that can open a browser (Claude, Cursor, VS Code, ChatGPT) | **OAuth with consent** — just add `https://mcp.murielai.io/mcp`; the client handles the rest |
| A headless agent that knows the person's email | **verified_email** — below |

Not supported: anonymous registration, and provider identity assertions (ID-JAG).

## 3. Register (verified_email)

```http
POST https://murielai.io/agent/auth
Content-Type: application/json

{"type": "verified_email", "email": "person@example.com",
  "client_name": "Acme Compliance Agent", "resource": "https://mcp.murielai.io/mcp"}
```

`resource` is optional: `https://mcp.murielai.io/mcp` (default, for the MCP server) or `https://murielai.io`
(for the HTTP tool API below).

Response `200`:

```json
{
  "client_id": "mur_c_…",
  "device_code": "mur_dc_…",
  "user_code": "BCDF-GHJK",
  "verification_uri": "https://murielai.io/agent/claim",
  "verification_uri_complete": "https://murielai.io/agent/claim?code=BCDF-GHJK",
  "expires_in": 900,
  "interval": 5,
  "token_endpoint": "https://murielai.io/oauth/token",
  "grant_type": "urn:ietf:params:oauth:grant-type:device_code"
}
```

Muriel emails the person a link to `verification_uri_complete`. Tell them to expect it and
show them `user_code` so they can check it matches.

## 4. Claim ceremony (the person)

The person opens the link, signs in **as that email address** (this is the verification),
sees your `client_name` and what you will be able to do, and clicks **Allow**. If they are
signed in as a different address, they are asked to switch.

## 5. Poll for the credential

```http
POST https://murielai.io/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=mur_dc_…&client_id=mur_c_…
```

Until the person answers you get `400` with `authorization_pending` — wait `interval`
seconds and retry. Then:

```json
{"access_token": "mur_at_…", "token_type": "Bearer", "expires_in": 3600,
  "refresh_token": "mur_rt_…", "scope": "muriel.read"}
```

The credential is issued once. Store the refresh token securely.

## 6. Use the access token

- **MCP** (resource `https://mcp.murielai.io/mcp`): connect to `https://mcp.murielai.io/mcp` over Streamable HTTP with
  `Authorization: Bearer <access_token>`.
- **HTTP tool API** (resource `https://murielai.io`): `GET https://murielai.io/api/mcp/tools` lists the tools;
  `POST https://murielai.io/api/mcp/call` with `{"name": "get_notable_events", "arguments": {"state": "CA"}}`
  runs one and returns `{"text": …, "is_error": …}`.

- **A2A** (either resource): JSON-RPC `SendMessage` at `https://murielai.io/a2a`; skills and an example
  call for each are on the Agent Card, `https://murielai.io/.well-known/agent-card.json`.

Access tokens last an hour. Refresh with `grant_type=refresh_token&refresh_token=…&client_id=…`
at the token endpoint; refresh tokens rotate on every use and last 60 days.

## 7. Errors

| Error | Where | What to do |
|---|---|---|
| `authorization_pending` | token | Keep polling every `interval` seconds |
| `slow_down` | token, register | Poll less often / wait an hour before registering again |
| `access_denied` | token | The person declined; stop |
| `expired_token` | token | 15 minutes passed; register again |
| `invalid_grant` | token | Code already used or unknown; register again |
| `unsupported_identity_type` | register | Use `verified_email` |
| `401` + `WWW-Authenticate` | API, MCP | Refresh the token; if that fails, register again |
| `429` | API, MCP | Rate limit; wait as `Retry-After` says |

## 8. Revocation

- You: `POST https://murielai.io/oauth/revoke` with `token` and `client_id` (RFC 7009). Revoking a
  refresh token ends the whole grant.
- The person: account page → Connected apps → Disconnect (https://murielai.io/account). Takes effect
  within a minute.

Full reference: https://murielai.io/developers#mcp-docs · Questions: info@murielai.io
