# auth.md — Uxify

You are an agent. This file lets you connect your user to Uxify **on their behalf**, following the [auth.md](https://github.com/workos/auth.md) profile:
register the user's email → send the user a link and a code → the user confirms in their browser → poll for an access token → use Uxify's MCP server.

Uxify measures how real visitors experience a website (speed, errors, conversions) and answers questions about it through Uxi, its AI analyst.

## Service details

- Service: Uxify (`https://app.uxify.com`)
- MCP server (what the token is for): `https://mcp.uxify.com/`
- Supported registration type: `service_auth` only. `identity_assertion` and `anonymous` are not supported.
- Pricing: https://uxify.com/pricing/ · Terms: https://uxify.com/legal/terms-of-service/ · Privacy: https://uxify.com/legal/privacy-policy/ · Contact: support@uxify.com

New accounts start on the free plan. Nothing is charged without the user choosing a paid plan in the Uxify app. Registering creates nothing: the account is only created or connected when the user confirms in the browser.

### Scopes

Every token is issued with all four:

| Scope | What it allows |
|---|---|
| `mcp:list_sites` | List the sites in the user's account. |
| `mcp:manage_sites` | Add a site and get its tracking snippet. |
| `mcp:ask_uxi` | Ask Uxi questions about the account. Each question counts toward the plan's Uxi prompts. |
| `mcp:ask_uxi_rum` | Query real-user monitoring data: Core Web Vitals, behavior, and agent activity. |

## Talking to the user

The user wants Uxify connected, not to learn how the connection works. Everything below this section is for you, not for them.

- Never mention tokens, assertions, claim tokens, expiry times, API endpoints, grant types, where you saved access, or alternative setups. The one exception: always send `claim.message_for_user` from Step 3, which contains the link and the code. The link is not a secret.
- The whole connection takes two short messages:
  1. `claim.message_for_user`, exactly as received (Step 4).
  2. When done: "Uxify is connected." Then offer the next useful thing, for example "Want me to add your site?"
- If a step fails, tell the user in one sentence what they need to do, nothing more.

## Step 1 — Discover

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

The authorization server metadata has an `agent_auth` block with the `identity_endpoint`, and lists the claim and JWT-bearer grants in `grant_types_supported`.

## Step 2 — Pick a method

Use `service_auth`. You need the user's email address: use the one you already know for them (they told you, or for example `git config user.email`). Ask only if you have none. If the user already has a Uxify account, use that account's email; they sign in instead of signing up.

If your client supports MCP OAuth (for example Claude Code: `claude mcp add --transport http uxify https://mcp.uxify.com/` then `/mcp`), that is an equally good alternative and you do not need this file.

## Step 3 — Register

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

{
  "type": "service_auth",
  "login_hint": "user@example.com",
  "client_name": "Claude Code"
}
```

`client_name` (optional, up to 60 characters) is your own name, such as `Claude Code`, `Codex` or `Cursor`. The confirmation page shows it to the user ("Connect Claude Code to Uxify"), so send it.

Response `200`:

```json
{
  "registration_id": "reg_01J...",
  "registration_type": "service_auth",
  "client_name": "Claude Code",
  "claim_token": "clm_...",
  "claim_token_expires": "2026-11-04T12:10:00.000Z",
  "post_claim_scopes": ["mcp:list_sites", "mcp:manage_sites", "mcp:ask_uxi", "mcp:ask_uxi_rum"],
  "claim": {
    "user_code": "123456",
    "expires_in": 600,
    "verification_uri": "https://app.uxify.com/agent/connect/k7q2xzp9m4tb8wfd",
    "interval": 5,
    "message_for_user": "To connect Uxify as user@example.com, open this link:\nhttps://app.uxify.com/agent/connect/k7q2xzp9m4tb8wfd\n\nand enter this code: 123456\n\nThe link is valid for 10 minutes. Want a different email? Just tell me."
  }
}
```

Keep `claim_token` in memory only. Never show it to the user and do not store it after Step 4.

## Step 4 — Send the link and code, then poll

**Before your first poll, send the user `claim.message_for_user` exactly as it is**, as normal text (not in a code block, so the link stays clickable). It contains the link and the code. Do not rephrase or shorten it, and do not leave the link out: the link is not a secret, and Uxify never emails it or the code, so without your message the user cannot finish. Send it even if you also open the link in the user's browser.

It looks like this:

> To connect Uxify as user@example.com, open this link:
> https://app.uxify.com/agent/connect/k7q2xzp9m4tb8wfd
>
> and enter this code: 123456
>
> The link is valid for 10 minutes. Want a different email? Just tell me.

The user signs in or signs up **as the email you registered**, picks a team, reviews what you will be able to do and types the code. The code is entered on Uxify's page; it never comes back to you.

The message names the email you registered and offers to change it. If the user gives a different email, stop polling, register again with the new email (Step 3) and send the new link and code. The old link cannot be used for another email.

Then poll the token endpoint every `claim.interval` seconds:

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

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

- `400 {"error": "authorization_pending"}` — keep polling.
- `400 {"error": "slow_down"}` — you polled too fast; wait longer between polls.
- `400 {"error": "expired_token"}` — the 10-minute window closed, too many wrong codes were entered, or the token was already exchanged. Tell the user the link expired and ask whether to send a new one; register again (Step 3) only if they agree.
- `200` — done:

```json
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 2592000,
  "scope": "mcp:list_sites mcp:manage_sites mcp:ask_uxi mcp:ask_uxi_rum",
  "identity_assertion": "eyJ...",
  "assertion_expires": "2026-11-04T13:05:00.000Z"
}
```

The claim token works once. The `access_token` is all you need. `identity_assertion` (valid one hour) is optional: you can ignore it.

## Step 5 — Keep the token for later sessions

The access token lasts **30 days**. Save it right away so later sessions can use Uxify without the user confirming again:

- **If your client can add MCP servers with a header, add Uxify for the user**, at user level so it works in every project. For Claude Code:

  ```bash
  claude mcp add --scope user --transport http uxify https://mcp.uxify.com/ --header "Authorization: Bearer <access_token>"
  ```

  Uxify's tools then show up natively in every new session.
- **Otherwise save the token where your future sessions will look and only the user can read it**: the OS keychain, or `~/.config/uxify/token` with mode `600`. Never inside the project or a repository.

Before you start a new connection, check whether you already have a saved token, and use it if it still works.

A `401` from the MCP server means the token expired or was revoked: delete the saved token (and the `uxify` server entry you added), then connect again from Step 3.

## Step 6 — Use the access token

The token is for Uxify's MCP server at `https://mcp.uxify.com/` (streamable HTTP, JSON-RPC). Send it as `Authorization: Bearer <access_token>`.

In the current session, call the server directly over HTTP (an MCP server you just added usually only loads in new sessions). Initialize a session, then call tools with the `Mcp-Session-Id` header from the initialize response:

```bash
curl -si https://mcp.uxify.com/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}'

curl -s https://mcp.uxify.com/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Mcp-Session-Id: $SESSION_ID" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
```

Useful tools for a new user, in order:

1. `add_website` — add the user's site to their account.
2. `get_install_snippet` — get the tracking snippet; add it to the site's `<head>` (or follow the Google Tag Manager steps).
3. `verify_installation` — confirm data is arriving once the site has had visits.
4. `ask_uxi_analytics` / `ask_uxi_action` — ask questions about the site's performance once data exists.

## Errors

| Where | Error | What to do |
|---|---|---|
| `/agent/identity` | `400 invalid_request` | `login_hint` must be a valid email; `client_name`, if sent, must be text of up to 60 characters. |
| `/agent/identity` | `400 unsupported_identity_type` | Use `"type": "service_auth"`. |
| `/agent/identity` | `429 slow_down` | Too many registrations for this email or network. Stop and tell the user to try again in an hour; do not retry in a loop. |
| `/oauth2/token` | `authorization_pending`, `slow_down`, `expired_token` | See Step 4. |
| `/oauth2/token` | `invalid_request` | Missing `claim_token`. |
| MCP server | `401` | Token expired or revoked. See Step 5. |

Do not retry a `4xx` other than `authorization_pending` and `slow_down` without changing the request.

## Revocation

When the user asks you to disconnect, revoke the access token, then delete it and any `uxify` server entry you added:

```http
POST https://app.uxify.com/oauth2/revoke
Content-Type: application/x-www-form-urlencoded

token=<access_token>
```

The response is `200` with an empty body, also for unknown or already revoked tokens.
