# Kapso authentication

Kapso supports OAuth Authorization Code with PKCE S256 for MCP clients. A user must sign in, select a project they can access, and approve the connection in their browser. Kapso publishes the WorkOS auth.md `agent_auth` metadata block (https://github.com/workos/auth.md) so agents can discover this document, but no agent registration type is enabled: `identity_types_supported` is empty and the advertised `identity_endpoint` rejects every registration. Use the Authorization Code flow below.

## Discover the MCP resource

The MCP endpoint is `https://api.kapso.ai/mcp`.

1. Read `https://api.kapso.ai/.well-known/oauth-protected-resource/mcp`. An unauthenticated MCP request also returns a 401 challenge with a `resource_metadata` URL in `WWW-Authenticate`.
2. Use the returned `resource` as the OAuth `resource` parameter. Read the authorization server's `/.well-known/oauth-authorization-server` metadata using the URL in `authorization_servers`.
3. Use the advertised `registration_endpoint`, `authorization_endpoint`, `token_endpoint`, and `revocation_endpoint`. Validate that the metadata `issuer` matches the authorization server you discovered.
4. The metadata also carries an `agent_auth` block whose `skill` points back to this document and whose `identity_endpoint` is the discovered authorization server's `/agent/identity` URL (for example `https://app.kapso.ai/agent/identity`). Its `identity_types_supported` list is empty, so skip agent registration and continue with the steps below.

Keep the same resource URL throughout authorization, token exchange, and refresh. If you use another supported Kapso API domain, start discovery on that domain and keep its returned URLs together.

## Register an MCP client

POST JSON to `registration_endpoint`, for example:

```json
{
  "client_name": "My MCP client",
  "redirect_uris": ["http://localhost:8765/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}
```

Use a callback your client controls. Redirect URIs must use HTTPS, except localhost HTTP callbacks. The response contains a `client_id`; public clients receive no client secret. Use the exact registered redirect URI, including its port and path.

Dynamic registration advertises granular MCP permissions in the response's `scope` field. Use the registration response and authorization server's `scopes_supported` metadata to discover available scopes, such as `mcp:conversations:read`, `mcp:messages:send`, and `mcp:setup:write`. The server also advertises `native:project`, `native:inbox`, and `native:workers` for configured first-party native clients; these scopes are not available through dynamic MCP registration. Client registration alone grants no project access.

## Request user consent

Generate a fresh cryptographically random PKCE `code_verifier` and a `code_challenge` equal to BASE64URL(SHA256(code_verifier)), without padding. Generate a random `state` and retain both values securely for this authorization attempt.

Open `authorization_endpoint` in the user's browser with URL-encoded query parameters:

```text
response_type=code
client_id=<registered client_id>
redirect_uri=<exact registered redirect URI>
scope=mcp:conversations:read%20mcp:messages:send
resource=<resource from discovery>
code_challenge=<PKCE challenge>
code_challenge_method=S256
state=<random state>
```

Request only the permissions your client needs, and include their read dependencies. For example, `mcp:messages:send` requires `mcp:conversations:read`; write/delete/run/publish permissions require the corresponding area's read scope, and team notification sending requires `mcp:notifications:read`. Requests missing a required dependency are rejected before consent with a message naming the missing permission. Kapso does not add unrequested permissions.

The legacy `mcp:project` request remains supported as a request for all MCP permissions available to consent. It does not issue a new broad grant: the user chooses explicit granular scopes. Existing grants containing `mcp:project` keep their previous access.

The user signs in, selects one project, and chooses Read only (the default), Full access, or Custom permissions. Consent is limited to the requested permissions and the user's project role; Full access does not bypass either limit. All-project access is not supported. At your callback, verify `state` matches the value you stored before accepting the authorization `code`.

## Exchange the code and call MCP

POST form-encoded fields to `token_endpoint`:

```text
grant_type=authorization_code
code=<callback code>
client_id=<registered client_id>
redirect_uri=<exact registered redirect URI>
code_verifier=<original PKCE verifier>
resource=<resource from discovery>
```

The response includes `access_token`, `token_type` (`Bearer`), `expires_in` (seconds), `refresh_token`, and `scope`. Store and use the actual `scope` returned by the token endpoint: the user may have approved fewer permissions than your client requested. Authorization codes are short-lived and single-use.

Send the access token to the MCP endpoint in the `Authorization: Bearer <access_token>` header. Access is bound to the approved project and scope.

## Refresh and revoke

Before the access token expires, POST form-encoded `grant_type=refresh_token`, `refresh_token=<current refresh token>`, and `resource=<same resource>` to `token_endpoint`.

Refresh rotates the tokens. Securely replace the stored access and refresh tokens with the returned pair, and serialize refresh requests for each connection. Reusing an older refresh token outside the short retry window revokes the grant. If refresh returns `invalid_grant`, start a new user authorization flow instead of repeatedly retrying the old token. Loss of project app access also prevents refresh.

To disconnect, POST form-encoded `token=<access or refresh token>` to `revocation_endpoint`. Revoking a recognized token revokes the entire associated grant, including its access and refresh tokens. The endpoint returns 200 for unknown tokens too; this does not prove a token was previously valid.

## Errors and credential handling

- Invalid registration metadata returns `invalid_client_metadata`; correct the registration request.
- Token errors include `invalid_grant`, `invalid_target`, and `unsupported_grant_type`. For `invalid_target`, check that the resource matches the authorization request. Do not retry unchanged invalid requests indefinitely.
- An MCP tool error with `code: insufficient_scope` lists `required_scopes`. Reconnect and ask the user to authorize the required access; refreshing a token preserves its current permissions.
- Rate-limited requests return 429; back off before retrying.
- Keep tokens and PKCE verifiers out of logs, URLs, source control, and shared transcripts. Store credentials securely and transmit them over HTTPS.
- User consent happens in the browser. There is no claim or device polling flow in this OAuth integration.
- A `POST` to the discovered `identity_endpoint` returns 400 with `anonymous_not_enabled`, `service_auth_not_enabled`, or `issuer_not_enabled` for every registration type. ID-JAG assertions, claim ceremonies, the JWT-bearer grant, and agent-auth event endpoints are not supported; do not retry registration, use the browser consent flow above.

Existing project API key connections are also supported by MCP. Use the project API key as a Bearer token, keep it private, and follow the MCP documentation at https://docs.kapso.ai/docs/whatsapp/mcp. Project API keys are separate from OAuth tokens and do not use the OAuth refresh or revocation endpoints.
