> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mavel.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> OAuth 2.0 with PKCE and dynamic client registration: how MCP clients authenticate, and the full protocol surface.

The MCP server is an OAuth-protected resource. Compliant clients handle
everything below automatically. You only need this page if you're building
your own client or debugging a connection.

## Discovery flow

1. The client POSTs to `/api/mcp` without a token and receives
   **`401 Unauthorized`** with a `WWW-Authenticate` header pointing at
   `https://mcp.mavel.ai/.well-known/oauth-protected-resource` (RFC 9728).
2. That document names the authorization server; the client then fetches
   `/.well-known/oauth-authorization-server` (RFC 8414) for the endpoints:
   authorize, token, register, revoke. The flow is authorization-code + PKCE
   (S256), public clients only.
3. The client self-registers (RFC 7591 dynamic client registration), runs the
   PKCE flow (you approve on the Mavel consent page), and retries with
   `Authorization: Bearer <token>`.

## Scopes

| Scope       | Granted when                  | Controls                    |
| ----------- | ----------------------------- | --------------------------- |
| `mcp:read`  | Automatically at registration | Read-only tools and prompts |
| `mcp:write` | You explicitly approve it     | Tools with side effects     |

## Registering a client (RFC 7591)

```bash theme={null}
curl -X POST https://mcp.mavel.ai/api/mcp/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP Client",
    "redirect_uris": ["http://localhost:3000/callback"]
  }'
```

* `redirect_uris`: 1-10 URIs; `https://` only, except `http://localhost` /
  `http://127.0.0.1`. URIs are matched on scheme + host + path (query and
  fragment ignored).
* Only public clients are issued. There is no `client_secret`, and
  `token_endpoint_auth_method` is always `none`.

## Tokens

* Access tokens expire after **1 hour** (`expires_in: 3600`).
* Refresh tokens live **90 days**; each refresh rotates the pair (the consumed
  refresh token is revoked and a fresh access + refresh pair is issued).
* Revoke with `POST /api/mcp/oauth/revoke` using either token: both belong to
  the same grant, so revoking one kills both (RFC 7009 semantics: HTTP 200
  regardless).

## Protocol surface

`POST /api/mcp` accepts JSON-RPC 2.0 (single object or batch array). Supported
methods: `initialize`, `tools/list`, `prompts/list`, `tools/call`,
`prompts/get`. Add `Accept: text/event-stream` only if your client requires
SSE responses.

**Rate limit:** 200 requests / minute / authenticated user; exceeding it
returns HTTP 429 with `X-RateLimit-*` headers.

### Error codes

| Code     | Meaning                                                                                                                        |
| -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `-32000` | Other typed tool error (plan limits, an expired onboarding session, bulk-limit caps); `error.data.code` names the specific one |
| `-32001` | Unauthenticated: no valid bearer token (HTTP 401)                                                                              |
| `-32002` | Missing scope (e.g. write tool without `mcp:write`)                                                                            |
| `-32601` | Unknown method, tool, or prompt name                                                                                           |
| `-32602` | Invalid params: an undeclared argument key, or an argument value the tool rejects (`error.data.code` starts with `MCP_ARG_`)   |
| `-32603` | Tool or prompt execution failed (generic to the caller)                                                                        |

### Strict argument validation

`tools/call` checks the top-level keys of `arguments` against the tool's
declared schema before running it. An argument key the schema doesn't list
(a typo like `offfset` for `offset`, for example) used to be silently
dropped; it now fails the whole call with `-32602`. The error message
itself names every offending key and the tool's legal argument names, for
example `unknown argument "offfset" for tool "list_chats" (valid:
project_id, date_from, ...)`, so the guidance holds even on clients that
strip `error.data`. The same list is also available structured, as
`error.data.valid_keys`.

If a key you're sure is spelled correctly still gets rejected, your cached
tool schema may be stale: re-fetch `tools/list` (see Server version below
for how to detect a stale cache automatically).

### Server version

`serverInfo.version`, returned at `initialize` and by an unauthenticated
`GET` request to the server URL, is the deployed commit SHA. Every
non-array `tools/call` result also carries that same value as a plain
`server_version` field in its response body, alongside
`_meta.server_version` (the MCP-spec channel): the body field is the
reliable one, since some MCP clients strip `_meta` (and `error.data`)
before the model ever sees them. `-32601` / `-32602` errors carry
`server_version` in `error.data` too, for clients that do deliver it, so
even a failed call can tell you which deploy answered it.

Compare the stamp across calls in the same session: if it changes, the
server redeployed and the tool catalog may have changed underneath you, so
re-fetch `tools/list`. There's no push notification for this
(`capabilities.tools.listChanged` is `false` by design, since this is a
stateless request/response transport with no server-to-client channel to
push on), so comparing the version yourself is the only way to detect a
mid-session deploy.
