Concepts
Authentication
OAuth 2.1 for apps that act on a person's behalf, and personal access tokens for tools and scripts. Both act as the person, limited to what they allow.
Which to use
| OAuth 2.1 | Personal access token | |
|---|---|---|
| For | Apps people connect: Meta Muse, ChatGPT, Claude, Grok, coding tools that sign in | Your scripts, CI, tools that take a header |
| How | The app sends the person to approve, then gets tokens | The person creates it in Studio and pastes it |
| Token | cc_oat_..., valid 1 hour, refreshed automatically | cc_pat_..., valid 30 to 365 days or no expiry |
| Limits | Scopes the app asked for, read-only if the person chose it, chosen Concierges | Scopes and Concierges chosen when created |
| Revoke | Developer Access, Connected apps; or POST /oauth/revoke | Developer Access, Tokens |
Either way, a token never does more than the person's own role on a Concierge allows, and every call is recorded in the audit log with the app's name.
OAuth 2.1
Covo follows the MCP authorization specification, so MCP clients discover everything on their own: call the MCP server without a token, read the WWW-Authenticate header, and follow it.
| What | Where |
|---|---|
| Protected resource metadata (RFC 9728) | https://covo.lanaai.io/.well-known/oauth-protected-resource/mcp (MCP) and https://covo.lanaai.io/.well-known/oauth-protected-resource (REST) |
| Authorization server metadata (RFC 8414) | https://covo.lanaai.io/.well-known/oauth-authorization-server |
| Authorize | https://covo.lanaai.io/oauth/authorize |
| Token | https://covo.lanaai.io/oauth/token (form or JSON body) |
| Dynamic client registration (RFC 7591) | https://covo.lanaai.io/oauth/register |
| Revocation (RFC 7009) | https://covo.lanaai.io/oauth/revoke |
| Issuer | https://covo.lanaai.io |
The flow
- The app calls
https://covo.lanaai.io/mcpwithout a token and gets401withWWW-Authenticate: Bearer resource_metadata="https://covo.lanaai.io/.well-known/oauth-protected-resource/mcp". - It reads that document, then the authorization server metadata it points to.
- It identifies itself: by a Client ID Metadata Document URL (preferred by ChatGPT and Claude), or by registering at
/oauth/register. - It sends the person to
/oauth/authorizewithresponse_type=code,client_id,redirect_uri,state,code_challengeandcode_challenge_method=S256, and optionallyscopeandresource. - The person signs in to Covo (if needed) and sees what the app asked for. They can make it read-only and limit it to chosen Concierges, then allow or cancel.
- Covo redirects to the app with
code,stateandiss(RFC 9207), or witherror=access_denied. - The app exchanges the code at
/oauth/tokenwithgrant_type=authorization_code,code,redirect_uri,client_idandcode_verifier, and gets an access token (1 hour) and a refresh token (30 days). - Before the access token expires, it sends
grant_type=refresh_token. Each refresh returns a new refresh token; the old one stops working.
Rules that keep people safe
- PKCE with S256 is required; plain challenges are refused.
- Codes are single use and expire after 10 minutes. A code used twice revokes everything issued with it.
- A refresh token used twice (a sign it was copied) revokes the whole approval; the person connects again.
- Redirect URIs must match what the app registered. https anywhere, http only on
localhostor127.0.0.1(any port, same path), or an app's own scheme for native apps. resource, when sent, must be this service (its origin,/mcpor/api/v1).- Publishing, rolling back and removing things still need
confirm: trueon each call, after the person agrees in the app.
Scopes
Send any of agents:read, agents:write, agents:delete, agents:test, agents:publish, conversations:read in scope, separated by spaces. With no scope, an app asks for agents:read agents:write. See Scopes and roles.
Registering a client
curl -X POST https://covo.lanaai.io/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name":"My agent","redirect_uris":["https://my-agent.example/oauth/callback"]}'The answer has your client_id. Public clients (the default, token_endpoint_auth_method: "none") rely on PKCE alone. Ask for client_secret_post or client_secret_basic to get a secret; it is shown once.
Exchanging the code
curl -X POST https://covo.lanaai.io/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=THE_CODE \
-d redirect_uri=https://my-agent.example/oauth/callback \
-d client_id=covo_client_... \
-d code_verifier=THE_VERIFIER{
"access_token": "cc_oat_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "cc_ort_...",
"scope": "agents:read agents:write"
}Errors follow RFC 6749: {"error": "invalid_grant", "error_description": "..."} with status 400 (401 for invalid_client).
Personal access tokens
- Create them in Studio: Settings, Developer Access, Create a token. They are shown once and stored only as a hash.
- Send them as
Authorization: Bearer cc_pat_...on every request, REST or MCP. - Choose a preset or exact scopes, every Concierge or chosen ones, and an expiry. Up to 50 active tokens per account.
- Name the client calling with
X-Client-NameandX-Client-Version(MCP clients send this ininitialize); it shows in Studio and in the audit log.
Never put a token in a URL, a prompt, a tool description or a log.