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

# MCP Authentication

> How the Evermuse MCP server authenticates clients with OAuth 2.1, which scopes it uses, how tokens work, and how to revoke access.

The Evermuse MCP server implements the
[MCP authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization). It is an OAuth 2.1
protected resource, and `api.evermuse.com` is both the resource server and the authorization server. Most MCP clients
handle this flow automatically: you only see a browser sign-in and consent screen.

API keys are also accepted for clients that can't run OAuth. See [API keys](#api-keys).

## What you see when you connect

<Steps>
  <Step title="Sign in">
    Your client opens `https://app.evermuse.com/oauth/authorize` in your browser. The page shows which application is
    asking to connect. Sign in with **Google**, **Microsoft** or **email**. If you don't have an Evermuse account yet,
    you can create one here.
  </Step>

  <Step title="Choose a workspace">
    If you belong to more than one workspace, choose the one the application should access. With exactly one
    workspace, it's selected for you. If you have none, you can create one, and Evermuse walks you through a short setup
    including creating your first product.
  </Step>

  <Step title="Review and approve">
    The consent screen names the application, the workspace and the permissions it will receive. Click
    **Allow access** to approve or **Deny** to cancel. Denying returns you to the client with an `access_denied` error.

    If your workspace has third-party MCP servers connected to Evermuse, you're asked instead whether to allow
    **Evermuse access only** or **Evermuse + 3rd party access**. Choose **Evermuse access only**: the MCP server doesn't
    give OAuth connections access to third-party tools, so the second option adds nothing.
  </Step>

  <Step title="Back to your agent">
    Evermuse redirects you back to your client, which exchanges the authorization code for tokens. The first time an
    application connects to a workspace, Evermuse sends you a confirmation email.
  </Step>
</Steps>

<Note>
  An authorization is always bound to **one workspace**. The agent can never read or write data in any other workspace
  you belong to.
</Note>

## Scopes

| Scope | Shown on the consent screen as | What it grants |
| - | - | - |
| `mcp:read` | Read your product data, meetings, and insights | Use the Evermuse tools in the authorized workspace. |
| `mcp:write` | Modify data and perform actions in your workspace | Additionally record and edit signals (`add_signals`, `update_signals`). |
| `mcp:thirdparty` | Access 3rd party tools connected to your workspace | Reserved for API keys. OAuth connections to the MCP server never receive third-party tools, whatever this scope says. |
| `openid` | Confirm who you are | Access to the `/oauth/userinfo` endpoint (`sub`). |
| `email` | See your email address | Adds `email` and `email_verified` to the `/oauth/userinfo` response. |

If a client doesn't send a `scope` parameter, Evermuse grants `mcp:read mcp:write`. That is the standard for
Claude, ChatGPT, Cursor, Codex and other MCP clients. Scopes that weren't part of the client's registration are dropped.

<Warning>
  `mcp:write` controls only the signal-writing tools. The other write tools, `add_source`, `create_shaping_note` and
  `update_shaping_note`, are available to every connection, including one granted only `mcp:read`.
</Warning>

<Info>
  Tools that write to your workspace are marked with `readOnlyHint: false` in their annotations, so your client can ask
  for confirmation before running them. See [Tools reference](/mcp/tools#write-tools) for the full list.
</Info>

## Technical reference

This section is for developers building their own MCP client or reviewing the integration.

### Discovery

| Endpoint | Purpose |
| - | - |
| `GET https://api.evermuse.com/.well-known/oauth-protected-resource` | Protected Resource Metadata ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728)) |
| `GET https://api.evermuse.com/.well-known/oauth-protected-resource/api/mcp` | Same document, path-inserted form |
| `GET https://api.evermuse.com/.well-known/oauth-authorization-server` | Authorization Server Metadata ([RFC 8414](https://www.rfc-editor.org/rfc/rfc8414)) |
| `GET https://api.evermuse.com/.well-known/openid-configuration` | Same document, for OpenID-style discovery |

A request to the MCP endpoint without credentials returns:

```http theme={null}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.evermuse.com/.well-known/oauth-protected-resource"
Content-Type: application/json

{"error":"Unauthorized","error_description":"Authentication required. Use Bearer token or x-api-key header."}
```

<AccordionGroup>
  <Accordion title="Protected Resource Metadata">
    ```json theme={null}
    {
      "resource": "https://api.evermuse.com/api/mcp",
      "authorization_servers": ["https://api.evermuse.com"],
      "scopes_supported": ["mcp:read", "mcp:write", "mcp:thirdparty", "openid", "email"],
      "bearer_methods_supported": ["header"],
      "resource_signing_alg_values_supported": ["RS256"]
    }
    ```
  </Accordion>

  <Accordion title="Authorization Server Metadata">
    ```json theme={null}
    {
      "issuer": "https://api.evermuse.com",
      "authorization_endpoint": "https://api.evermuse.com/oauth/authorize",
      "token_endpoint": "https://api.evermuse.com/oauth/token",
      "registration_endpoint": "https://api.evermuse.com/oauth/register",
      "userinfo_endpoint": "https://api.evermuse.com/oauth/userinfo",
      "response_types_supported": ["code"],
      "grant_types_supported": ["authorization_code", "refresh_token"],
      "code_challenge_methods_supported": ["S256"],
      "token_endpoint_auth_methods_supported": ["none"],
      "scopes_supported": ["mcp:read", "mcp:write", "mcp:thirdparty", "openid", "email"],
      "claims_supported": ["sub", "email", "email_verified"]
    }
    ```
  </Accordion>
</AccordionGroup>

Both metadata documents are cacheable for one hour.

### Endpoints

| Endpoint | Method | Description |
| - | - | - |
| `/oauth/register` | `POST` | Dynamic Client Registration ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)) |
| `/oauth/authorize` | `GET` | Starts the authorization code flow and redirects to the Evermuse consent page |
| `/oauth/token` | `POST` | Exchanges an authorization code or a refresh token for tokens |
| `/oauth/revoke` | `POST` | Token revocation ([RFC 7009](https://www.rfc-editor.org/rfc/rfc7009)) |
| `/oauth/userinfo` | `GET`/`POST` | Returns `sub`, plus `email` and `email_verified` with the `email` scope. Requires `openid`. |

All endpoints are on `https://api.evermuse.com`.

### Client registration

Evermuse supports three ways for a client to identify itself:

<Tabs>
  <Tab title="Dynamic Client Registration">
    `POST /oauth/register` with a JSON body. `redirect_uris` is required. Every redirect URI must use `https://`, or
    `http://` with a `localhost` or `127.0.0.1` host. Evermuse issues a public client (no secret).

    ```bash theme={null}
    curl -X POST https://api.evermuse.com/oauth/register \
      -H "Content-Type: application/json" \
      -d '{
        "client_name": "My MCP Client",
        "redirect_uris": ["http://localhost:8787/callback"],
        "grant_types": ["authorization_code", "refresh_token"],
        "response_types": ["code"],
        "token_endpoint_auth_method": "none"
      }'
    ```

    The response is `201 Created` with the registered metadata and a `client_id`. Registration is limited to
    10 requests per hour per IP address.
  </Tab>

  <Tab title="Client ID Metadata Document">
    A client can use an `https://` URL as its `client_id`. Evermuse fetches the JSON document at that URL, which must
    contain `client_id` and `redirect_uris`. The fetch has a 5-second timeout and a 64 KB size limit,
    doesn't follow redirects, and is cached for 5 minutes.
  </Tab>

  <Tab title="Pre-registered clients">
    Evermuse ships client metadata for popular MCP clients (Claude, ChatGPT, Cursor, VS Code and Codex) at
    `https://api.evermuse.com/oauth/clients/{slug}`, for example `https://api.evermuse.com/oauth/clients/claude`. These
    URLs work as Client ID Metadata Documents. Most clients use Dynamic Client Registration instead, and both paths
    lead to the same consent flow.
  </Tab>
</Tabs>

### Authorization code flow with PKCE

<Steps>
  <Step title="Authorize">
    Redirect the user to:

    ```text theme={null}
    https://api.evermuse.com/oauth/authorize
      ?response_type=code
      &client_id=CLIENT_ID
      &redirect_uri=REDIRECT_URI
      &code_challenge=BASE64URL_SHA256_OF_VERIFIER
      &code_challenge_method=S256
      &state=RANDOM_STATE
      &scope=mcp:read%20mcp:write
    ```

    `response_type`, `client_id`, `redirect_uri`, `code_challenge`, `code_challenge_method` and `state` are required.
    Only `S256` is accepted. The `redirect_uri` must exactly match one of the client's registered URIs. `scope` is
    optional. Evermuse redirects to its consent page, and after approval back to `redirect_uri` with `code` and `state`.
    Authorization requests and codes expire after 10 minutes, and codes are single-use.
  </Step>

  <Step title="Exchange the code">
    ```bash theme={null}
    curl -X POST https://api.evermuse.com/oauth/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d grant_type=authorization_code \
      -d code=AUTHORIZATION_CODE \
      -d redirect_uri=REDIRECT_URI \
      -d client_id=CLIENT_ID \
      -d code_verifier=CODE_VERIFIER
    ```

    ```json theme={null}
    {
      "access_token": "em_at_…",
      "refresh_token": "em_rt_…",
      "token_type": "bearer",
      "expires_in": 3600
    }
    ```
  </Step>

  <Step title="Call the MCP server">
    Send the access token on every request:

    ```bash theme={null}
    curl -X POST https://api.evermuse.com/api/mcp \
      -H "Authorization: Bearer em_at_…" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
    ```
  </Step>

  <Step title="Refresh">
    ```bash theme={null}
    curl -X POST https://api.evermuse.com/oauth/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d grant_type=refresh_token \
      -d refresh_token=em_rt_… \
      -d client_id=CLIENT_ID
    ```

    Each refresh returns a **new** refresh token. Store it and discard the old one.
  </Step>
</Steps>

### Tokens

| Token | Format | Lifetime |
| - | - | - |
| Authorization code | Opaque | 10 minutes, single use |
| Access token | Opaque, prefixed `em_at_` | 1 hour |
| Refresh token | Opaque, prefixed `em_rt_` | 30 days, rotated on every use |

* Tokens are opaque random strings, not JWTs. Evermuse stores only a SHA-256 hash of each token.
* **Refresh token rotation:** every refresh issues a new refresh token and retires the old one. A retired refresh token
  presented again within 30 seconds (for example, a retried request) returns `invalid_grant` without side effects.
  Presented after that, it is treated as a replay: Evermuse revokes the entire token family, and the user must
  reconnect.
* Tokens stop working automatically if the user leaves or is removed from the authorized workspace, or the account is
  deleted. Tool calls are refused at once. Other requests may keep succeeding for up to a minute while caches expire.
* A `401` for an invalid or expired token doesn't carry a `WWW-Authenticate` header. Clients should treat any `401`
  with `error: "invalid_token"` as a signal to refresh, and run the full flow again if the refresh fails.

### Revoking access

The Evermuse app doesn't yet have a page that lists connected applications. To end a connection:

* **From your AI client:** disconnect or remove the Evermuse connector. Clients that revoke tokens on disconnect call
  `/oauth/revoke`. Otherwise the access token expires within an hour, and the refresh token expires after 30 days
  without use.
* **From the confirmation email:** reply to the email Evermuse sent when the application first connected, and our team
  will revoke it.
* **By leaving the workspace:** removing a user from a workspace invalidates all of their tokens for that workspace.
* **Programmatically:** `POST /oauth/revoke` with a `token` parameter. Revoking a refresh token revokes its whole
  token family, including access tokens issued from it. The endpoint always returns `200 OK`, as RFC 7009 requires.

```bash theme={null}
curl -X POST https://api.evermuse.com/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d token=em_rt_…
```

### OAuth errors

OAuth endpoints return errors as `{"error": "...", "error_description": "..."}`:

| Error | Typical cause |
| - | - |
| `invalid_request` | A required parameter is missing, or `code_challenge_method` isn't `S256`. |
| `invalid_client` | Unknown `client_id`. |
| `invalid_redirect_uri` | The `redirect_uri` isn't registered for the client, or (during registration) isn't HTTPS or loopback. |
| `invalid_client_metadata` | A registration request without valid `redirect_uris`. |
| `invalid_scope` | None of the requested scopes are available to the client. |
| `invalid_grant` | Expired or reused code, PKCE verification failed, mismatched `redirect_uri` or `client_id`, or an invalid, expired or replayed refresh token. |
| `unsupported_grant_type` | A grant other than `authorization_code` or `refresh_token`. |
| `unsupported_response_type` | A `response_type` other than `code`. |
| `access_denied` | The user clicked **Deny** on the consent screen. |
| `rate_limit_exceeded` | Too many requests. Retry after `retry_after` seconds. |

## API keys

For server-to-server use, or clients that only support a static header, you can authenticate to the MCP server with a
workspace API key instead of OAuth.

1. In Evermuse, go to **Settings → API Keys** and click **Create API Key**. See [Authentication](/authentication#creating-an-api-key) for screenshots.
2. Enable the `mcp:read` permission, and `mcp:write` if the agent should record or edit signals. MCP permissions require
   the key to have a default product.
3. Click **Generate** and copy the key. It is shown only once.
4. Send it in the `x-api-key` header:

```bash theme={null}
curl -X POST https://api.evermuse.com/api/mcp \
  -H "x-api-key: em_sk_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

An API key acts as the user who created it, in that key's workspace. If a request carries both a bearer token and an
API key, the bearer token wins. Keys are limited to 1,000 MCP requests per hour each. Revoke a key at any time from
**Settings → API Keys**.

<Warning>
  Treat API keys like passwords. Never put them in client-side code or commit them to source control. Prefer OAuth for
  personal use in AI assistants.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.