# MCP and OAuth API reference

CalmLens runs a remote MCP server at `https://api.calmlens.com/mcp`, with OAuth 2.1 sign-in, so AI assistants can work in a project for the person who approves them. Standard MCP clients discover everything here on their own; this page is for developers building a client or debugging one. To connect an existing assistant, see [AI assistants](/docs/guides/ai-assistants).

## MCP endpoint

| Property          | Value                                                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
| URL               | `https://api.calmlens.com/mcp`                                                                                           |
| Transport         | Streamable HTTP. Send each JSON-RPC message with `POST`; responses are JSON, never an event stream. `GET` returns `405`. |
| Sessions          | None: each request stands alone and no `Mcp-Session-Id` is issued.                                                       |
| Protocol versions | `2025-11-25`, `2025-06-18`, `2025-03-26` and `2024-11-05`. An unsupported `MCP-Protocol-Version` header returns `400`.   |
| Batches           | A JSON array of up to 20 messages, as protocol `2025-03-26` allows, returns an array of replies.                         |
| Authentication    | `Authorization: Bearer <key>`, where the key comes from the OAuth flow below. A project API key also works.              |

A request without a valid key returns `401` with a `WWW-Authenticate` header that points at the protected resource metadata, which is how clients find the sign-in:

```text
WWW-Authenticate: Bearer resource_metadata="https://api.calmlens.com/.well-known/oauth-protected-resource/mcp"
```

The server answers `initialize`, `ping`, `tools/list` and `tools/call`; notifications get `202` with no body.

### Tools

Each tool runs one [API](/docs/api) endpoint with the caller's key, so the endpoint's validation, permissions and errors apply. The [AI assistants guide](/docs/guides/ai-assistants#tools) lists the tools and their endpoints.

- **Arguments.** Path and query parameters are top-level arguments, named as in the API. A request body goes in `body`. `projectId` defaults to the key's project.
- **Results.** A tool returns the API's JSON response as text content. When the API returns an error, the result has `isError: true` and the error body, with its `code`.
- **Annotations.** Read-only tools set `readOnlyHint` and deleting tools set `destructiveHint`, so clients can ask before running them. Keys approved for reading only aren't offered tools that change things.

## Discovery

| Endpoint                                                                | Returns                                                                                                                                                             |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET https://api.calmlens.com/.well-known/oauth-protected-resource/mcp` | Protected resource metadata (RFC 9728): `resource`, `authorization_servers` and the `read` and `write` scopes.                                                      |
| `GET https://api.calmlens.com/.well-known/oauth-authorization-server`   | Authorization server metadata (RFC 8414): the endpoints below, `S256` PKCE, the `authorization_code` grant and public clients (`token_endpoint_auth_method: none`). |

## Register a client

`POST https://api.calmlens.com/oauth/register` registers a public client (RFC 7591). Clients have no secret: they prove themselves with PKCE.

```json
{
  "client_name": "My assistant",
  "redirect_uris": ["https://assistant.example/oauth/callback"]
}
```

| Field           | Rules                                                                                                                                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `redirect_uris` | One to ten. HTTPS on any host except CalmLens's own domain, HTTP on `localhost`, `127.0.0.1` or `[::1]` (any port), or an app's own scheme such as `cursor://`. No fragments, credentials, or script and data URLs. |
| `client_name`   | Shown on the consent page, reduced to plain letters, digits, spaces and simple punctuation. Names that read as CalmLens are replaced with the first redirect URI's host.                                            |

The response is `201` with `client_id` and the registered values. Clients that never receive a key are deleted after a week; register again if yours was.

## Authorize

Send the person's browser to `https://calmlens.com/oauth/authorize`. They sign in, see the app's name and where approving sends them, and choose a project and access.

| Parameter               | Value                                                                                                                                               |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `response_type`         | `code`                                                                                                                                              |
| `client_id`             | From registration.                                                                                                                                  |
| `redirect_uri`          | Exactly one you registered.                                                                                                                         |
| `code_challenge`        | Base64url SHA-256 of your code verifier.                                                                                                            |
| `code_challenge_method` | `S256`                                                                                                                                              |
| `state`                 | Optional, returned unchanged.                                                                                                                       |
| `scope`                 | Optional. `read` alone offers read-only access; otherwise the person chooses read only or read and change.                                          |
| `resource`              | Optional (RFC 8707). Must be `https://api.calmlens.com/mcp`, `https://api.calmlens.com` or `https://api.calmlens.com/v1`; anything else is refused. |

Approval redirects to your `redirect_uri` with `code`, your `state` and `iss` (`https://api.calmlens.com`, RFC 9207). Declining redirects with `error=access_denied`.

## Exchange the code

`POST https://api.calmlens.com/oauth/token` with a form body:

| Parameter       | Value                                            |
| --------------- | ------------------------------------------------ |
| `grant_type`    | `authorization_code`                             |
| `code`          | From the redirect. Valid once, for five minutes. |
| `code_verifier` | The verifier behind your challenge.              |
| `client_id`     | The client the code was issued to.               |
| `redirect_uri`  | The same URI as in the authorization request.    |
| `resource`      | Optional, as above.                              |

```json
{
  "access_token": "clsk_…",
  "token_type": "Bearer",
  "scope": "read write",
  "project_id": "123e4567-e89b-42d3-a456-426614174000"
}
```

The access token is a project API key holding part of the approving person's access. It works as the `Bearer` key for `/mcp` and as `X-Api-Key` on the [REST API](/docs/api#authentication). It has no expiry and there is no refresh token: it lasts until it's revoked, or until the person no longer holds the access they approved. A code presented a second time fails and revokes the key it issued.

The CLI signs in with the device authorization grant (RFC 8628) at `POST https://api.calmlens.com/oauth/device/code`, which is reserved for the CalmLens CLI; other clients get `unauthorized_client`.

## Revoke

`POST https://api.calmlens.com/oauth/revoke` with a form body of `token` revokes that key (RFC 7009). It always returns `200`, and only revokes keys issued through OAuth. People can also disconnect apps from their [Profile](/portal/profile).

## Errors and limits

OAuth endpoints answer errors as `{ "error", "error_description" }` with status `400` unless noted:

| `error`                   | Meaning                                                                                                      |
| ------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `invalid_request`         | A required parameter is missing.                                                                             |
| `invalid_redirect_uri`    | Registration with a redirect URI outside the rules above.                                                    |
| `invalid_grant`           | The code is unknown, expired, already used, or doesn't match the client, redirect URI, verifier or resource. |
| `unsupported_grant_type`  | Use `authorization_code`.                                                                                    |
| `unauthorized_client`     | The device grant, for clients other than the CLI.                                                            |
| `slow_down`               | `429`, with `Retry-After`: registration and device sign-ins are limited to ten a minute per address.         |
| `temporarily_unavailable` | `503`: try again later.                                                                                      |

The consent page reports problems in the browser rather than redirecting: an unregistered client or redirect URI, or a `resource` other than CalmLens.
