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

# Authentication

> Both API keys and OAuth 2.0 access tokens work over MCP, using the same Bearer header the protocol requires.

The MCP server uses the same credentials as the [direct REST API](/authentication/overview) — an [API key](/authentication/api-keys) or an [OAuth 2.0](/authentication/oauth2) access token. There's nothing to register or configure specifically for MCP.

## Always send `Bearer`

The Model Context Protocol's HTTP transport requires every request to carry its credential as:

```
Authorization: Bearer <token>
```

This is different from the direct REST API, where an API key is normally sent with the `token` prefix (see [API Keys](/authentication/api-keys)) and only an OAuth 2.0 access token uses `Bearer`. Over MCP, **always use `Bearer`, regardless of which kind of credential you're sending** — the server inspects the token and routes it to the verification path that recognizes it. You don't need to know or declare which kind it is.

<CodeGroup>
  ```bash Using an API key theme={null}
  curl https://mcp.lofty.com/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -H "Authorization: Bearer <YOUR_API_KEY>" \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_custom_fields","arguments":{}}}'
  ```

  ```bash Using an OAuth access token theme={null}
  curl https://mcp.lofty.com/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_custom_fields","arguments":{}}}'
  ```
</CodeGroup>

Both requests above are identical apart from which credential is in the header — the server tells them apart for you.

## Permissions and rate limits

A call made through `invoke_api` is authenticated and authorized exactly like a direct call to the same endpoint:

* It runs as the caller identified by the token, with that caller's existing permissions. An operation the caller couldn't perform through the CRM directly (or through the REST API) is refused here too, with the same error.
* It's subject to the same [rate limits](/authentication/overview#rate-limiting) as a direct REST call to that endpoint — using MCP doesn't grant a separate quota.
* OAuth 2.0 access tokens are still scoped to whatever your application was granted in the [Developer Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login). Calling an operation your app hasn't been granted returns the same `403` / `200100` your app would get calling the REST API directly.

## Credential visibility and revocation

MCP doesn't introduce a separate credential store — an API key or OAuth token you hand to an MCP client is the same one you'd use for a direct REST call, managed the same way:

* **API keys** — view, rotate, or revoke yours under **Settings → Integrations → API** in the Lofty CRM. There's no separate list of "which MCP clients are using this key" — revoking the key there immediately stops every caller using it, MCP or otherwise.
* **OAuth 2.0 tokens** — manage your application's grants in the [Developer Portal](https://api.lofty.com/vendor/frontend/static/index.html#/login); revoking or expiring an access token there has the same immediate effect on MCP calls as on direct REST calls.

If an MCP client is going to hold a long-lived credential (most are configured once and left running), treat it like any other long-lived integration credential — scope it to the narrowest permissions that work, and rotate it if the client or its host ever changes hands.

## Invalid or missing credentials

If the credential is missing, expired, or doesn't resolve to a real user or application, `invoke_api` surfaces the underlying API's own error rather than a generic MCP failure — for example:

```json theme={null}
{"content":[{"type":"text","text":"The Lofty API returned HTTP 401: {\"message\":\"...\"}"}],"isError":true}
```

See [Error Codes](/authentication/error-codes) for what each code means.
