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

# MCP Authentication

> How OAuth 2.1 step-up authorization works for the Closient MCP server's protected tools.

The Closient MCP server is an OAuth 2.1 resource server, classified
per [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) (Protected
Resource Metadata) and the
[MCP Authorization spec](https://modelcontextprotocol.io/specification/draft/basic/authorization).

Most tools are public — no token, no setup, frictionless adoption
for AI agents discovering Closient through `tools/list`. A smaller
set of tools is gated behind a scoped OAuth token: `generate_qr_url`
(touches the paid QR-generation pipeline) and the brand-owner
product write tools added in C-4038 (create/edit products, images,
documents, videos, nutrition, ingredients — see
[Tool Reference](/guides/mcp-tools)). The pattern is extensible:
future protected tools join the same step-up flow.

## Two roles, two endpoints

Closient acts as both authorization server and resource server in
this stack:

| Role                  | What it does                                                                  | Discovery endpoint                                                                                           |
| --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Authorization Server  | Issues and validates OAuth tokens, hosts the consent screen, manages refresh. | [`/.well-known/oauth-authorization-server`](https://www.closient.com/.well-known/oauth-authorization-server) |
| Resource Server (MCP) | Validates the bearer token, enforces scopes, dispatches the tool call.        | [`/.well-known/oauth-protected-resource`](https://www.closient.com/.well-known/oauth-protected-resource)     |

The two discovery endpoints conform to RFC 8414 (authorization
server metadata) and RFC 9728 (protected resource metadata)
respectively. Compliant MCP clients fetch them automatically — you
do not need to configure URLs by hand.

## Public tools — no token

These tools accept anonymous calls. No header, no setup:

* `ping`
* `validate_gtin`
* `resolve_gtin`
* `lookup_product`
* `search_products`
* `get_product_detail`
* `compare_products`
* `check_availability`

If you pass a valid token alongside one of these calls, Closient
records the authenticated principal in the audit log (see
[MCP Identity Propagation](/guides/mcp-identity)) but the call
behaves identically. This is forward-compat plumbing for future
features like "authenticated tier: higher rate limit."

## Protected tools — step-up flow

`generate_qr_url` requires a bearer token with the `qr:generate`
scope; the product write tools require `products:write` or
`dashboard:write` depending on which tool (see [Scopes](#scopes)
below). The step-up flow is what makes the user experience smooth:
the client doesn't have to authenticate up front, only when it
actually tries to use a protected tool.

The product write tools carry one additional requirement beyond
scope: they need an **end-user** token — one minted via the
`authorization_code` grant, where a real person approved the
consent screen. A `client_credentials` token (an agent acting on its
own behalf with no consenting end user) is rejected even with the
right scope, because every write is attributed to a specific brand-
owner user in the audit trail. `generate_qr_url` has no such
restriction.

```mermaid theme={null}
sequenceDiagram
  participant Agent as MCP Client (Claude/Cursor/VS Code)
  participant Closient as Closient MCP Server
  participant User

  Agent->>Closient: tools/call generate_qr_url
  Closient-->>Agent: 403 + WWW-Authenticate: Bearer scope="qr:generate"
  Note over Agent: MCP client reads header, starts OAuth flow
  Agent->>User: Browser opens consent screen
  User->>Closient: Approves "Allow <Client> to generate QR URLs"
  Closient-->>Agent: authorization_code → exchanged for access_token
  Agent->>Closient: tools/call generate_qr_url + Authorization: Bearer <token>
  Closient-->>Agent: 200 + { qr_url, gtin14, host }
```

Step by step:

1. **First call without a token.** The MCP client calls the tool
   anonymously. Closient returns `403 Forbidden` with this header:

   ```
   WWW-Authenticate: Bearer error="insufficient_scope" scope="qr:generate"
       resource_metadata="https://www.closient.com/.well-known/oauth-protected-resource"
   ```

2. **Client reads `resource_metadata`** and fetches the protected
   resource metadata document to learn the authorization server URL
   and supported scopes.

3. **Authorization flow.** The client opens the consent screen in
   the user's browser (using PKCE per the OAuth 2.1 spec), the user
   approves, and the client exchanges the authorization code for an
   access token + refresh token.

4. **Retry with the token.** The client retries `tools/call
   generate_qr_url` with `Authorization: Bearer <token>`. Closient
   validates the token, confirms the `qr:generate` scope, and
   dispatches the call.

The whole flow happens transparently in Tier 1 MCP clients (Claude
Desktop, Cursor, VS Code) — the user sees a single consent prompt
and then the tool just works.

## Scopes

MCP-protected tools use three scopes today. `products:write` and
`dashboard:write` are the same per-app `:write` scopes REST-API
callers use (`APP_API_REGISTRY`) — no MCP-specific scope machinery
was needed to add the C-4038 write tools, only new tool
registrations against existing scopes. Future protected tools will
register additional scopes following the same `domain:action`
convention.

| Scope             | Required for                                                                                                                                                                                                                                             | Description                                                                                     |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `qr:generate`     | `generate_qr_url`                                                                                                                                                                                                                                        | Generate GS1 Digital Link URLs for QR/Data Matrix encoding. Counts against the user's QR quota. |
| `products:write`  | `create_product`, `update_product`                                                                                                                                                                                                                       | Create and edit product identity, dimensions/weight, and GPC classification.                    |
| `dashboard:write` | `upload_product_image`, `delete_product_image`, `upload_product_document`, `update_product_document`, `delete_product_document`, `add_product_video`, `update_product_video`, `delete_product_video`, `set_product_nutrition`, `set_product_ingredients` | Manage product media, nutrition facts, and ingredients.                                         |

REST-API scopes (`accounts:read`, `products:read`, etc.) are
independent of MCP in the sense that holding one does not by itself
authenticate an MCP call — but `products:write` and `dashboard:write`
are literally the same scope strings the REST API's OAuth-authenticated
endpoints check, so a token minted for one surface works on the
other's write tools/endpoints for the same permission.

## Token lifetimes

| Token         | Lifetime |
| ------------- | -------- |
| Access token  | 24 hours |
| Refresh token | 90 days  |

When an access token expires, the MCP client uses the refresh
token to mint a new one without prompting the user. Expired refresh
tokens require the user to consent again.

## Client registration

The MCP spec (Nov 2025 revision) supports three registration paths.
All work against Closient:

1. **Client ID Metadata Documents (CIMD)** — preferred. The client
   advertises its metadata at a public URL; Closient fetches it on
   first contact. No upfront registration required.
2. **Dynamic Client Registration (RFC 7591)** — the client `POST`s
   to the authorization server's `/register` endpoint and receives
   a `client_id` + `client_secret`. Useful for clients that want a
   stable identity but don't have a public metadata URL.
3. **Pre-registration** — for known clients like Claude Desktop and
   VS Code, Closient maintains pre-registered application records.
   No action on your part; the client uses its built-in `client_id`.

Tier 1 MCP clients pick the right registration path automatically.

## Manual token minting

For local development, scripted integrations, or MCP clients that
don't implement the OAuth flow, you can mint a token by hand:

```bash theme={null}
# 1. Register a client (one-time)
curl -X POST https://www.closient.com/o/register/ \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "my-mcp-test-client",
    "redirect_uris": ["http://localhost:8765/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "scope": "qr:generate"
  }'

# 2. Send the user through the consent flow
open "https://www.closient.com/o/authorize/?response_type=code&client_id=<CLIENT_ID>\
&redirect_uri=http://localhost:8765/callback&scope=qr:generate&code_challenge=<PKCE>&code_challenge_method=S256"

# 3. Exchange the resulting code for an access token
curl -X POST https://www.closient.com/o/token/ \
  -d 'grant_type=authorization_code' \
  -d 'code=<AUTH_CODE>' \
  -d 'redirect_uri=http://localhost:8765/callback' \
  -d 'client_id=<CLIENT_ID>' \
  -d 'code_verifier=<PKCE_VERIFIER>'
```

Then call the protected tool with `Authorization: Bearer <access_token>`.

## Verifying scope coverage

A quick way to sanity-check that a token has the right scope before
calling a protected tool:

```bash theme={null}
curl -sS https://www.closient.com/mcp/http \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"generate_qr_url","arguments":{"gtin":"00614141123452"}}}'
```

A successful response confirms the token holds `qr:generate`. A
`403` with `error="insufficient_scope"` means the token is valid
but missing the scope — re-run the consent flow and accept the
`qr:generate` request.

## Audit & identity

Every tool call (public or protected) emits one structured
`closient.mcp.audit` log line with the agent's `client_id` and (for
`authorization_code` tokens) the consenting end user. See
[MCP Identity Propagation](/guides/mcp-identity) for the full audit
schema and the `get_current_end_user()` accessor for tool authors.

For protected tools, `@user_action` on the underlying service-layer
function (e.g. `apps.products.services.qr_url.build_digital_link_url`)
also records a durable database audit row — the answer to "who
generated this QR code?" is queryable forever from the
`audit_events` table.

## See also

* [Install the MCP Server](/guides/mcp-install)
* [Tool Reference](/guides/mcp-tools)
* [MCP Error Contract](/guides/mcp-errors)
* [MCP Identity Propagation](/guides/mcp-identity)
* [Authentication (REST API)](/authentication) — API-key auth for the REST API, independent of MCP OAuth.
* [RFC 9728 — OAuth 2.0 Protected Resource Metadata](https://www.rfc-editor.org/rfc/rfc9728)
* [MCP Authorization spec (draft)](https://modelcontextprotocol.io/specification/draft/basic/authorization)
