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.
Authorization server metadata
GET https://app.lucenthq.com/.well-known/oauth-authorization-server
Returns the endpoints, supported parameters, and capabilities.
client_credentials are not
supported. plain PKCE is rejected.
Dynamic Client Registration
POST https://app.lucenthq.com/api/oauth/register
Implements RFC 7591.
Rate-limited per IP — busy clients should cache the resulting
client_id rather than re-register.
Request
| Field | Required | Notes |
|---|---|---|
redirect_uris | yes | At least one. Each must be in the allowlist. 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. |
201 Created)
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 anInvalidClientMetadataError 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 |
https://user:pass@…) or fragments
(#…) are rejected per RFC 6749 §3.1.2. To add another hosted
origin, email [email protected].
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. |
/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
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
Response
| 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
and is idempotent — always returns 200 even for unknown tokens.
Tools and scopes
Every token currently carries the singleread:lucent scope. The four
read-only tools (list_signals, list_issues, get_issue,
list_insights) are gated behind that scope at the MCP handler level
via withMcpAuth({ requiredScopes: ["read:lucent"] }).
See the tool reference for arguments and return shapes.