# Authenticating an agent to Clueso

Clueso's agent-facing API is an MCP server at `https://connect.clueso.io/mcp`. It speaks
Streamable HTTP and requires an OAuth 2.1 bearer token for every call that touches a
workspace. This document is the prose walkthrough of how an agent gets one.

Everything below is machine-discoverable — you should not need to read this file to
authenticate. It exists so that you can check your understanding against prose when a
discovery document is ambiguous.

## Discover

Call the MCP endpoint without credentials. It answers `401` with a spec-shaped challenge:

```
WWW-Authenticate: Bearer resource_metadata="https://connect.clueso.io/.well-known/oauth-protected-resource/mcp"
```

Fetch that URL for RFC 9728 protected-resource metadata:

```
GET https://connect.clueso.io/.well-known/oauth-protected-resource
```

```json
{
  "resource": "https://connect.clueso.io/mcp",
  "authorization_servers": ["https://auth.clueso.io/oauth/2.1"]
}
```

Then fetch RFC 8414 authorization-server metadata for the endpoints and supported
parameters:

```
GET https://connect.clueso.io/.well-known/oauth-authorization-server
```

Both discovery documents live on `connect.clueso.io`, the host that serves the API — **not**
on `www.clueso.io`, which serves only this document and the site. Resolve them as absolute
URLs; there is nothing at `https://www.clueso.io/.well-known/oauth-authorization-server`.

Note that `initialize` and `tools/list` answer without a token, so you can inspect the
server's capabilities and its 40 tools before asking a user for consent.

## Pick a method

There is one method: **OAuth 2.1 authorization code with PKCE (`S256`)**. The
authorization server advertises `code_challenge_methods_supported: ["S256"]` and
`grant_types_supported: ["authorization_code", "refresh_token"]`.

There are no API keys, no client-credentials grant, and no service accounts. Every token
is bound to a human Clueso user and to the workspaces that user can reach. An agent acting
without a signed-in user cannot call this API.

Clueso does **not** currently implement the identity-assertion (`id-jag`) token-exchange
profile. Do not attempt an `identity_assertion` exchange against these endpoints — use the
authorization-code flow below.

## Register

The authorization server supports RFC 7591 dynamic client registration.

```
agent_auth:
  register_uri:   https://connect.clueso.io/register
  claim_uri:      (none — see below)
  revocation_uri: (none — see Revocation)
```

`register_uri` is the `registration_endpoint` from the RFC 8414 metadata. POST your client
metadata to it:

```
POST https://connect.clueso.io/register
```

There is deliberately no `claim_uri` and no `revocation_uri`. Clueso does not implement the
identity-assertion (`id-jag`) profile, and revocation is a user action in the Clueso UI
rather than a client-callable endpoint — so no URI is advertised for either. Do not probe
for them; an advertised URI that does not resolve is worse than an absent one.

Register once, cache the credentials you get back, and reuse them. Public clients that
cannot keep a secret should register with `token_endpoint_auth_method: "none"` and rely on
PKCE; the token endpoint accepts `none`, `client_secret_basic` and `client_secret_post`.

## Claim

Run the authorization-code flow. There is no separate claim endpoint and no `claim_uri`.

1. Generate a PKCE verifier and its `S256` challenge.
2. Send the user to
   `https://auth.clueso.io/oauth/2.1/authorize` with `response_type=code`,
   your `client_id`, `redirect_uri`, `code_challenge`, `code_challenge_method=S256`,
   `state`, and `scope=openid email profile`.
   Pass `resource=https://connect.clueso.io/mcp` — the server supports resource
   indicators (RFC 8707) and will audience-bind the token.
3. The user signs in and approves. Clueso redirects back with `code` and `state`, plus an
   `iss` parameter you should verify.
4. Exchange the code at `https://auth.clueso.io/oauth/2.1/token` with your
   `code_verifier`.

Available scopes are `openid`, `email` and `profile`. Request the least you need.

## Use the credential

Send the access token as a bearer token on every MCP request:

```
POST https://connect.clueso.io/mcp
Authorization: Bearer <access_token>
Accept: application/json, text/event-stream
Content-Type: application/json
```

Before creating anything, call `find(type='workspaces')` and confirm with the user which
workspace they mean. Tokens frequently reach more than one.

## Errors

| Status | Meaning | What to do |
|---|---|---|
| `401` with `WWW-Authenticate: Bearer` | No token, or the token expired | Refresh, or restart discovery from the `resource_metadata` URL in the header |
| `401` without a refreshable token | The refresh token is spent or revoked | Send the user through the authorization flow again |
| `403` | Authenticated, but not entitled to that workspace or resource | Do not retry. Ask the user to switch workspace or grant access |
| JSON-RPC error result | The tool call itself failed | Read `error.message`; it is written for agents and says what to change |

An unauthenticated request returns `{"error":"Please provide a Bearer token to connect."}`
rather than an HTML page, so you can branch on the body as well as the status.

## Revocation

Users revoke agent access from their Clueso workspace settings. Revocation invalidates the
refresh token immediately; the next call returns `401` and you should restart discovery
rather than retry with the same credentials. There is no separate `revocation_uri` for
clients to call — the authorization server does not advertise one, so do not probe for it.

Questions about agent access: privacy@clueso.io
