# OAuth and tokens

> What the OAuth consent screen asks, how the connection shows up and is revoked, API token roles and scopes, and the lite profile.

The MCP server accepts two kinds of credential, and both are organization tokens with a role: an **OAuth connection**, created when you grant access from the client, and an **API token** that you create yourself. Either way the assistant can never do more than you can.

## Connecting with OAuth

The server implements [MCP authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) (OAuth 2.1). `mcp.openwaf.dev` is the protected resource and `dash.openwaf.dev`, where you sign in, the authorization server. From your side:

1. You give the client only the URL `https://mcp.openwaf.dev/mcp`.
2. The client registers itself and opens `dash.openwaf.dev` in the browser. If you are not signed in, you sign in and come back to the same request.
3. The **consent screen** shows the client's name (declared by the client itself, so it is marked as unverified) and the host it will return to, and asks you to choose:
   - **Organization:** one of yours. Suspended organizations and those pending approval cannot be chosen.
   - **Role:** read only (`readonly`), technician (`tech`) or administrator (`admin`), never above your own role. A member limited to one client can choose `readonly` or `tech`.
   - **Expiry:** 7, 30 or 90 days.
4. You allow or deny. Denying returns `access_denied` to the client.


If the client's return address is not one it registered, the dashboard shows the error and does **not** redirect anywhere. Other errors are shown with a «Return to the application» button and the destination host, never an automatic redirect.

## Where the connection shows up

The connection is a token of the organization: it appears in **API tokens** as «MCP · <client>», with the «OAuth app» badge, the role and the expiry you chose. Like any token, its role never exceeds the member's current role, and it dies if the member leaves the organization.

**To revoke access**, revoke it in **API tokens**: its access and refresh tokens stop working at once. The client can also revoke it itself.

Behind the scenes the client holds a 1-hour access token (`owfa_…`) and a single-use refresh token (`owfr_…`); only the SHA-256 of each secret is stored. An OAuth access token only opens the MCP endpoint: the rest of the REST API answers 401.

In the audit log you will find `oauth.authorize` (access granted, with client, return host, role and expiry), `token.create` and `token.revoke`.

## API tokens

For clients without OAuth, or for automation, create a token of its own for the assistant in the dashboard (**API tokens**) or with `POST /v1/orgs/{org}/tokens`, with the narrowest role and an expiry:

| Choice | Effect |
|---|---|
| `readonly` | The assistant only sees the read tools; the write tools do not even appear in `tools/list` |
| `tech` | Read and write zones, records, edge settings and rules |
| `admin` | Everything, including the egress write tools and `audit_log` |
| Limited to a client (`client_id`) | The assistant only sees that client's zones |
| Limited to a zone (`zone_id`) | The assistant only sees that zone |

`audit_log` needs `admin`, or `tech` limited to a client. Zones outside the token's scope answer `not_found`, as if they did not exist. Tokens `owf_…` work the same on every URL of the server.

> **Caution: A token is a credential**
>
> It goes in the MCP client's configuration, never in the conversation.


## Lite profile

The full tool list takes about 54 KB of `tools/list` (around 12,500 tokens), loaded into every conversation. The `lite` profile lists only discovery, a code mode and the everyday reads (about 1,300 tokens):

| Tool | Purpose |
|---|---|
| `search_tools` | Searches tools and API operations by keywords (English or Spanish) |
| `describe_tool` | The full input schema of a tool or API operation, an example and, for writes, the simulate → confirm protocol |
| `call_tool` | Calls any tool by name, for clients that only call listed tools |
| `execute` | Code mode: a read-only JavaScript script run on the server, with the token's permissions; it can simulate writes but never apply them |
| `list_zones`, `get_zone`, `list_records` | The everyday reads |

Every other tool **still exists** and can be called by name or through `call_tool`, with the same permissions. Choose it with the `profile` URL parameter or the `X-OpenWAF-MCP-Profile: lite` header; an unknown value answers 400. **Without the parameter the profile is `full`**, so clients already configured do not change.

**Claude Code**

```sh
claude mcp add --transport http openwaf-lite 'https://mcp.openwaf.dev/mcp?profile=lite' \
  --header "Authorization: Bearer $OPENWAF_TOKEN"
```
**Cursor**

```json
{ "mcpServers": { "openwaf": { "type": "http", "url": "https://mcp.openwaf.dev/mcp?profile=lite",
  "headers": { "Authorization": "Bearer owf_…" } } } }
```
