Skip to content
Covo Developers

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.1Personal access token
ForApps people connect: Meta Muse, ChatGPT, Claude, Grok, coding tools that sign inYour scripts, CI, tools that take a header
HowThe app sends the person to approve, then gets tokensThe person creates it in Studio and pastes it
Tokencc_oat_..., valid 1 hour, refreshed automaticallycc_pat_..., valid 30 to 365 days or no expiry
LimitsScopes the app asked for, read-only if the person chose it, chosen ConciergesScopes and Concierges chosen when created
RevokeDeveloper Access, Connected apps; or POST /oauth/revokeDeveloper 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.

WhatWhere
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
Authorizehttps://covo.lanaai.io/oauth/authorize
Tokenhttps://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
Issuerhttps://covo.lanaai.io

The flow

  1. The app calls https://covo.lanaai.io/mcp without a token and gets 401 with WWW-Authenticate: Bearer resource_metadata="https://covo.lanaai.io/.well-known/oauth-protected-resource/mcp".
  2. It reads that document, then the authorization server metadata it points to.
  3. It identifies itself: by a Client ID Metadata Document URL (preferred by ChatGPT and Claude), or by registering at /oauth/register.
  4. It sends the person to /oauth/authorize with response_type=code, client_id, redirect_uri, state, code_challenge and code_challenge_method=S256, and optionally scope and resource.
  5. 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.
  6. Covo redirects to the app with code, state and iss (RFC 9207), or with error=access_denied.
  7. The app exchanges the code at /oauth/token with grant_type=authorization_code, code, redirect_uri, client_id and code_verifier, and gets an access token (1 hour) and a refresh token (30 days).
  8. 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 localhost or 127.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, /mcp or /api/v1).
  • Publishing, rolling back and removing things still need confirm: true on 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
200 response
{
  "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-Name and X-Client-Version (MCP clients send this in initialize); it shows in Studio and in the audit log.

Never put a token in a URL, a prompt, a tool description or a log.