> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lucenthq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth reference

> Protocol-level details for the Lucent OAuth 2.1 authorization server that fronts the MCP endpoint.

Lucent's MCP endpoint is fronted by an OAuth 2.1 authorization server
that conforms to the [MCP
specification](https://modelcontextprotocol.io). For the user-facing
setup walkthrough, see the [quickstart](/mcp/quickstart). This page
documents the protocol surface for client implementers.

## Discovery

Both metadata documents are publicly readable and CORS-open.

### Protected resource metadata

`GET https://app.lucenthq.com/.well-known/oauth-protected-resource`

Returns the resource server's identity and which authorization servers
issue tokens for it.

```json theme={null}
{
  "resource": "https://app.lucenthq.com/api/mcp",
  "authorization_servers": ["https://app.lucenthq.com"],
  "scopes_supported": ["read:lucent"]
}
```

### Authorization server metadata

`GET https://app.lucenthq.com/.well-known/oauth-authorization-server`

Returns the endpoints, supported parameters, and capabilities.

```json theme={null}
{
  "issuer": "https://app.lucenthq.com",
  "authorization_endpoint": "https://app.lucenthq.com/oauth/authorize",
  "token_endpoint": "https://app.lucenthq.com/api/oauth/token",
  "registration_endpoint": "https://app.lucenthq.com/api/oauth/register",
  "revocation_endpoint": "https://app.lucenthq.com/api/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
  "revocation_endpoint_auth_methods_supported": ["none", "client_secret_post"],
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["read:lucent"]
}
```

The only supported grant flow is **authorization code with PKCE
(S256)**. Implicit, password, and `client_credentials` are not
supported. `plain` PKCE is rejected.

## Dynamic Client Registration

`POST https://app.lucenthq.com/api/oauth/register`

Implements [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591).
Rate-limited per IP — busy clients should cache the resulting
`client_id` rather than re-register.

**Request**

```json theme={null}
{
  "client_name": "Acme Connector",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "read:lucent"
}
```

| Field                        | Required | Notes                                                                                          |
| ---------------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `redirect_uris`              | yes      | At least one. Each must be in the [allowlist](#allowed-redirect-uris). HTTPS or loopback only. |
| `client_name`                | no       | ≤ 64 chars; letters, numbers, spaces, `-_.()`. Defaults to `Unnamed Client`.                   |
| `token_endpoint_auth_method` | no       | `none` (public client + PKCE, default) or `client_secret_post`.                                |
| `grant_types`                | no       | Subset of `["authorization_code", "refresh_token"]`. Defaults to both.                         |
| `response_types`             | no       | Must include `code`. Defaults to `["code"]`.                                                   |
| `scope`                      | no       | Defaults to `read:lucent`.                                                                     |

**Response (`201 Created`)**

```json theme={null}
{
  "client_id": "luc_client_…",
  "client_secret": "…",
  "client_name": "Acme Connector",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "read:lucent"
}
```

`client_secret` is only present when `token_endpoint_auth_method` is
`client_secret_post`. Public clients (the default for native MCP
clients) get `client_id` only and authenticate with PKCE.

### Allowed redirect URIs

The redirect allowlist defends against open-redirect and auth-code
injection. Registration rejects URIs outside this list with an
`InvalidClientMetadataError` and an opaque message.

| Type            | Allowed values                                                             |
| --------------- | -------------------------------------------------------------------------- |
| Hosted (HTTPS)  | `https://claude.ai`, `https://claude.com`                                  |
| Loopback (HTTP) | `http://127.0.0.1`, `http://[::1]`, `http://localhost` — any port and path |

URIs with embedded credentials (`https://user:pass@…`) or fragments
(`#…`) are rejected per RFC 6749 §3.1.2. To add another hosted
origin, email [support@lucenthq.com](mailto:support@lucenthq.com).

## Authorization request

`GET https://app.lucenthq.com/oauth/authorize?…`

Standard OAuth 2.1 authorization-code request with PKCE:

| Param                   | Required | Notes                                                     |
| ----------------------- | -------- | --------------------------------------------------------- |
| `response_type`         | yes      | Must be `code`.                                           |
| `client_id`             | yes      | From registration.                                        |
| `redirect_uri`          | yes      | Must exactly match one of the registered `redirect_uris`. |
| `code_challenge`        | yes      | 43–128 chars, base64url-encoded SHA-256 of the verifier.  |
| `code_challenge_method` | yes      | Must be `S256`.                                           |
| `state`                 | rec.     | Opaque value the client uses for CSRF protection.         |
| `scope`                 | no       | Defaults to `read:lucent`.                                |

If the user isn't signed in to Lucent, they're routed through `/login`
and returned to the consent screen after authentication. If a consent
record already exists for this `(client_id, scope)` and hasn't been
revoked, the consent screen is bypassed and the browser is redirected
back to `redirect_uri` with `?code=…&state=…` immediately.

If anything is wrong (unknown client, revoked client, redirect\_uri
mismatch, malformed PKCE), the user sees a single opaque error page —
no detail is leaked that would let an unauthenticated probe enumerate
client state.

## Token endpoint

`POST https://app.lucenthq.com/api/oauth/token`

`Content-Type: application/x-www-form-urlencoded`. Returns
`Cache-Control: no-store`.

### `grant_type=authorization_code`

```http theme={null}
POST /api/oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=luc_oac_…
&redirect_uri=https://claude.ai/api/mcp/auth_callback
&client_id=luc_client_…
&code_verifier=…
&client_secret=…   (only if token_endpoint_auth_method=client_secret_post)
```

The `redirect_uri` must match the value sent at authorize time (RFC
6749 §4.1.3 / OAuth 2.1 §4.1.3). Codes are single-use and expire after
60 seconds. Reuse of an already-redeemed code revokes any tokens
previously issued from it.

### `grant_type=refresh_token`

```http theme={null}
grant_type=refresh_token
&refresh_token=luc_ort_…
&client_id=luc_client_…
&scope=read:lucent          (optional, must be ⊆ originally granted)
&client_secret=…            (only if confidential client)
```

Refresh tokens **rotate** on every exchange — the old refresh token is
marked rotated and a new pair is returned. If a rotated refresh token
is presented again, the entire token chain (access + refresh) for that
session is revoked as a reuse-detection cascade.

### Response

```json theme={null}
{
  "access_token": "luc_oat_…",
  "refresh_token": "luc_ort_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:lucent"
}
```

| TTL                | Default    | Env var                                |
| ------------------ | ---------- | -------------------------------------- |
| Access token       | 1 hour     | `OAUTH_ACCESS_TOKEN_TTL_SECONDS`       |
| Refresh token      | 30 days    | `OAUTH_REFRESH_TOKEN_TTL_DAYS`         |
| Authorization code | 60 seconds | `OAUTH_AUTHORIZATION_CODE_TTL_SECONDS` |

## Revocation endpoint

`POST https://app.lucenthq.com/api/oauth/revoke`

Implements [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009)
and is idempotent — always returns `200` even for unknown tokens.

```http theme={null}
POST /api/oauth/revoke HTTP/1.1
Content-Type: application/x-www-form-urlencoded

token=luc_oat_… or luc_ort_…
&token_type_hint=access_token | refresh_token   (optional)
&client_id=luc_client_…
&client_secret=…   (only if confidential client)
```

Use this when a user signs out of your client. Lucent also exposes a
**Connected apps** section in the dashboard so end users can
unilaterally revoke any registered client.

## Tools and scopes

Every token carries the `read:lucent` scope, which gates the whole MCP
handler via `withMcpAuth({ requiredScopes: ["read:lucent"] })` and covers
the four read tools (`list_signals`, `list_issues`, `get_issue`,
`list_insights`). The write tool, `update_issue`, additionally requires
`write:issues` — clients must request that scope during authorization,
and tokens without it get a missing-scope error from the tool.

See the [tool reference](/mcp/tools) for arguments and return shapes.

## Errors

OAuth-protocol errors follow the [RFC 6749
§5.2](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2) JSON
shape:

```json theme={null}
{ "error": "invalid_grant", "error_description": "PKCE verification failed" }
```

User-visible authorize-page failures collapse to a single opaque
message. Token-endpoint client-auth failures are also opaque (no
distinction between "unknown client\_id" and "wrong secret") — both
defenses are deliberate and prevent enumeration of registered clients.
