# Sonder auth.md

You are an agent. Sonder's MCP server lets you work in a Sonder workspace as one of its members, with that member's permissions. This file is how you register a client and get a token. The machine-readable source of truth is the Protected Resource Metadata; if anything here disagrees with it, follow the metadata.

Use one address for every workspace:

- Resource server (the MCP endpoint): `https://sonderlabs.co.uk/mcp`
- Authorization server (the issuer): `https://sonderlabs.co.uk`

The member says which workspace during sign-in, then signs in to that workspace and approves your client there. Your token then reaches that workspace only.

Each workspace is also its own MCP server and authorization server, at `https://{workspace}.sonderlabs.co.uk/mcp` and `https://{workspace}.sonderlabs.co.uk` (for example https://demo.sonderlabs.co.uk). The steps are the same there, with that origin in place of https://sonderlabs.co.uk and no workspace question; a workspace's own server also takes client ID metadata documents.

## 1. Discover

Call the MCP endpoint without a token, or fetch the metadata directly:

```http
POST /mcp HTTP/1.1
Host: sonderlabs.co.uk
Content-Type: application/json
```

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://sonderlabs.co.uk/.well-known/oauth-protected-resource", scope="beastdash"
```

`GET https://sonderlabs.co.uk/.well-known/oauth-protected-resource` returns:

```json
{
  "resource": "https://sonderlabs.co.uk",
  "authorization_servers": [
    "https://sonderlabs.co.uk"
  ],
  "scopes_supported": [
    "beastdash"
  ],
  "bearer_methods_supported": [
    "header"
  ],
  "resource_name": "Sonder MCP"
}
```

Then `GET https://sonderlabs.co.uk/.well-known/oauth-authorization-server` (also served at `/.well-known/openid-configuration`) for the endpoints: `authorization_endpoint` `/oauth/authorize`, `token_endpoint` `/oauth/token`, `registration_endpoint` `/oauth/register`, `revocation_endpoint` `/oauth/revoke`. The only scope is `beastdash`: everything the approving member's own role allows.

## 2. Register a client

Dynamic client registration (RFC 7591). No identity is needed:

```http
POST /oauth/register HTTP/1.1
Host: sonderlabs.co.uk
Content-Type: application/json

{"client_name": "My agent", "redirect_uris": ["http://127.0.0.1:33418/callback"], "token_endpoint_auth_method": "none"}
```

```json
{"client_id": "sgc_…", "client_id_issued_at": 1794500000, "client_name": "My agent", "redirect_uris": ["http://127.0.0.1:33418/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "beastdash"}
```

Give one to ten `redirect_uris`: https anywhere, or http on localhost, 127.0.0.1 or ::1 (any port matches). `token_endpoint_auth_method` is `none` (a public client, the default), `client_secret_post` or `client_secret_basic`; with a secret method the response also carries `client_secret`.

## 3. Get the member's approval

A person has to approve: there is no token without a member of the workspace signing in. Send the user's browser to the authorization endpoint with PKCE:

```http
GET /oauth/authorize?response_type=code&client_id=sgc_…&redirect_uri=http%3A%2F%2F127.0.0.1%3A33418%2Fcallback&code_challenge=…&code_challenge_method=S256&state=…&scope=beastdash&resource=https%3A%2F%2Fsonderlabs.co.uk%2Fmcp HTTP/1.1
Host: sonderlabs.co.uk
```

The member types their workspace: the first part of the address they open Sonder at. If you know it, add `workspace=demo` (for example) to skip the question. They then sign in to that workspace, see your client's name and where it sends them back, and choose Allow. The browser comes back to your `redirect_uri` with `code`, `state` and `iss` (check that `iss` is `https://sonderlabs.co.uk`). The code lasts two minutes.

## 4. Exchange the code

```http
POST /oauth/token HTTP/1.1
Host: sonderlabs.co.uk
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=…&redirect_uri=http%3A%2F%2F127.0.0.1%3A33418%2Fcallback&code_verifier=…&client_id=sgc_…&resource=https%3A%2F%2Fsonderlabs.co.uk%2Fmcp
```

```json
{"access_token": "sga_…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "sgr_…", "scope": "beastdash"}
```

`resource`, if sent, must be `https://sonderlabs.co.uk` or `https://sonderlabs.co.uk/mcp` (`invalid_target` otherwise).

## 5. Call the MCP server

Send `Authorization: Bearer <access_token>` on every request to `POST https://sonderlabs.co.uk/mcp` (Streamable HTTP, one JSON response per request; protocol versions 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26). List the tools with `tools/list`: they depend on the areas the workspace has switched on. Tool calls are limited to 60 a minute per member.

## 6. Refresh and revoke

- Access tokens last an hour. Refresh with `grant_type=refresh_token&refresh_token=…&client_id=…` at `/oauth/token`. Refresh tokens rotate: use each one once.
- An approval lasts 30 days; after that, ask the member to approve again. It also ends if the member disconnects your app in their workspace's Connect to AI settings, or their role in the workspace changes.
- Revoke a token with `POST /oauth/revoke` (`token=…`, form-encoded).

## Errors

| Error | Where | What to do |
| --- | --- | --- |
| 401 with `WWW-Authenticate` | /mcp | Get a token (steps 2 to 4), or refresh it. |
| `invalid_redirect_uri` | /oauth/register | Use https, or http on localhost. |
| `invalid_grant` | /oauth/token | The code or refresh token is expired, used or revoked: start again at step 3. |
| `invalid_target` | /oauth/token | Send https://sonderlabs.co.uk/mcp as `resource`. |
| 503 `temporarily_unavailable` | /oauth/*, /mcp | Try again shortly. |
