# auth.md — authenticating to the Kairon MCP server

_Generated 2026-10-18 by the site build; canonical at https://heykairon.com/auth.md. The live metadata documents below are the source of truth._

You are an agent. Every call to `https://app.heykairon.com/mcp` needs a bearer token that a **person** authorized: Kairon acts on that person's own LinkedIn seat, so there is no anonymous, service or agent-only identity. The flow is OAuth 2.1 with PKCE and dynamic client registration, and every URL below is discoverable from the two metadata documents.

## Step 1 — Discover

1. `GET https://app.heykairon.com/.well-known/oauth-protected-resource` (RFC 9728) — `resource` is `https://app.heykairon.com/mcp`; `authorization_servers` names the issuer. The same document is at `https://app.heykairon.com/.well-known/oauth-protected-resource/mcp`.
2. `GET https://app.heykairon.com/.well-known/oauth-authorization-server` (RFC 8414) — the `authorization_endpoint`, `token_endpoint`, `registration_endpoint`, `scopes_supported` (`openid profile email offline_access`), `code_challenge_methods_supported` (`S256`).
3. An unauthenticated `POST https://app.heykairon.com/mcp` answers `401` with `WWW-Authenticate: Bearer resource_metadata="https://app.heykairon.com/.well-known/oauth-protected-resource"`, which is the same first hop.

## Step 2 — Pick a method

There is one: the authorization-code grant with PKCE, completed by a person in a browser. Identity assertions (ID-JAG), service-account registration and claim ceremonies are **not supported**; a client that has no person to send to the authorization endpoint cannot obtain a token.

## Step 3 — Register

`POST` the `registration_endpoint` (RFC 7591, dynamic client registration) with your `redirect_uris` and `token_endpoint_auth_method` (`none` for a public client). The response carries the `client_id` you use from here on. Registration needs no credential.

## Step 4 — Authorize

Send the person to the `authorization_endpoint` with `response_type=code`, your `client_id`, a `redirect_uri` you registered, `code_challenge` (S256) and the scopes. They sign in to Kairon (there is no separate consent screen: a signed-in person is the consent); the code returns to your redirect URI.

## Step 5 — Exchange the code

`POST` the `token_endpoint` with `grant_type=authorization_code`, the `code`, the `code_verifier` and your `client_id`. You receive an `access_token` (30 days) and, with `offline_access`, a `refresh_token` (90 days); refresh with `grant_type=refresh_token`.

## Step 6 — Use the access_token

`POST https://app.heykairon.com/mcp` with `Authorization: Bearer <access_token>` and `Accept: application/json, text/event-stream` (MCP Streamable HTTP, JSON-RPC). `initialize`, then `tools/list`, then `tools/call`. The catalogue is the same for every caller; a tool a seat cannot use refuses with a reason in its own result, never at the door.

## Errors

- `401` with `WWW-Authenticate` — no token, an invalid one, or an expired one: go back to Step 1 or refresh.
- `405` on `GET` or `DELETE /mcp` — the endpoint is stateless and takes `POST` only.
- A tool-level refusal comes back as a JSON-RPC result with `isError: true` and a code such as `CHANNEL_ACTION_NOT_CONNECTED` (the caller has no LinkedIn seat), `SUBSCRIPTION_REQUIRED` (the organization has no subscription) or `ORGANIZATION_DEACTIVATED`; the body is `{ code, params, detail }`, and `detail` says what to do.

## Revocation

There is no revocation endpoint and no self-serve revocation yet. An access token expires 30 days after it was issued and a refresh token after 90; to cut a client off before that, write to admin@heykairon.com. Tokens are opaque and introspected on every call, so once revoked on our side a token fails with `401` on its next use.

Related: [llms.txt](https://heykairon.com/llms.txt) · [the MCP page](https://heykairon.com/mcp) · [the CLI](https://heykairon.com/cli), which runs this same flow with `kairon login`.
