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

# MCP authentication

> How Reintersect gets a credential for each member: your own OAuth server, or a token Reintersect signs for every call.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

An MCP server can authenticate Reintersect's calls in three ways. An app picks one with the `auth`
option of `.addMcp(handler, { auth })`; there is no default. Custom servers that an admin adds by URL
always use OAuth.

| `auth` | Who issues the token | Use it when |
| - | - | - |
| `"oauth"` | Your authorization server | Your server already supports MCP authorization, or you want members to sign in to their own accounts. Your handler checks the token. |
| `"reintersect"` | Reintersect, per call | You have no auth stack and want to know which Reintersect member and workspace is calling. The SDK checks the token before your handler runs. |
| `"none"` | Nobody | The tools are public and need no identity. |

## Bring your own OAuth

With `auth: "oauth"`, Reintersect acts as a standard MCP OAuth client. You don't configure
anything in Reintersect beyond the server URL; it discovers the rest:

1. It calls your MCP endpoint without a token, reads the `WWW-Authenticate` challenge and fetches
   your Protected Resource Metadata.
2. It reads your authorization server's metadata from the issuer listed there.
3. It registers itself as a client. It uses a static client an admin entered for this server if
   there is one. Otherwise it uses its Client ID Metadata Document when your authorization server
   advertises support for those, with the document's URL as the client ID. Dynamic Client
   Registration is the fallback; Reintersect registers without a `scope` parameter, because some
   authorization servers reject the resource scopes MCP clients usually send.
4. It sends the member through your authorization flow with PKCE and handles the redirect.

| Value | Where Reintersect uses it |
| - | - |
| `https://api.reintersect.com/oauth/mcp-client-metadata.json` | Client ID (Client ID Metadata Document) |
| `https://api.reintersect.com/oauth/mcp-jwks.json` | Keys for `private_key_jwt` client authentication |
| `https://api.reintersect.com/integrations/mcp/callback` | Redirect URI |

The metadata document declares `token_endpoint_auth_method: "private_key_jwt"`. If your
authorization server keeps an allowlist of redirect domains, add `api.reintersect.com`.

### Static clients

Some providers support neither Client ID Metadata Documents nor Dynamic Client Registration. For
those, a workspace admin creates an OAuth client in the provider's console with the redirect URI
above, then enters its client ID and secret in the server's settings under
**Settings → Workspace → Integrations**. Reintersect stores the secret encrypted.

### What Reintersect requires from your server

Reintersect follows the MCP authorization specification and refuses servers that would weaken it:

| Requirement | What Reintersect does |
| - | - |
| PKCE with S256 | Your authorization server metadata must list `S256` in `code_challenge_methods_supported`. If the field is missing, Reintersect refuses to connect. |
| Discovered endpoints | Reintersect never guesses `/authorize` and `/token` on your server's origin when metadata is missing. |
| Resource indicators | Authorization and token requests carry the `resource` parameter set to your MCP server's URL. |
| Issuer check | When your authorization server returns `iss` on the redirect, Reintersect validates it against the discovered issuer before reading the result. |
| State | Every authorization request carries a single-use `state` that the callback must return unchanged. |

### One connection per member

An admin installing an app or adding a server only makes it available. Each member connects their
own account, and the agent only ever uses the token of the member who started the turn, even in a
shared conversation.

The first time the agent needs a tool from a server the member hasn't connected, it pauses and
shows a **Connect** card inline in the chat. The member signs in in a new tab, and the agent resumes
the turn as soon as the callback completes.

Reintersect stores each member's tokens encrypted and refreshes them itself. Refreshes for one
member and server are serialized, so several agent turns running at once never race to use the same
refresh token.

### Asking for more access

If a tool call fails with an `insufficient_scope` error, the agent shows a **Grant more access** card
naming the scopes your server asked for. When the member approves, Reintersect requests the new
scopes together with every scope the member granted before, so reconnecting never drops access.

## Reintersect-signed tokens

If you don't run an authorization server, use `auth: "reintersect"`:

```ts src/app.ts theme={null}
import { createReintersectApp } from "@reintersect/sdk/app";
import { mcpServer } from "./mcp";

export const app = createReintersectApp({ name: "Launchpad", slug: "launchpad" }).addMcp(mcpServer, {
  auth: "reintersect",
});
```

Reintersect then signs a short-lived JWT for every request to your MCP server and sends it as
`Authorization: Bearer`. Members don't need to connect anything; the token says which member, in
which workspace, is calling through which install.

| Claim | Value |
| - | - |
| `iss` | `https://api.reintersect.com` |
| `aud` | Your MCP server's exact URL |
| `sub` | The member's ID |
| `act` | `{ "sub": "agent:<conversationId>" }` when the agent makes the call |
| `rei_workspace` | Workspace ID |
| `rei_install` | Install ID |
| `rei_app` | App ID |
| `scope` | Space-separated scopes granted to the install |
| `exp` | At most 5 minutes after `iat` |
| `iat`, `jti` | Issue time and a unique token ID |

Tokens are signed with ES256. The public keys are at
`https://api.reintersect.com/.well-known/reintersect/jwks.json`. Reintersect only ever sends a token
to the URL in its `aud` claim, so a token can't be replayed against a different server.

With `auth: "reintersect"`, the SDK verifies the token before your handler runs. It checks:

* the signature against the published keys;
* the issuer;
* the audience (your server's exact URL);
* the token type;
* an expiry of at most five minutes;
* that `rei_app` is your app, when you pass `appId` (hosted apps always check their own). Other apps'
  tokens are rejected with the reason `AppMismatch`.

A missing or invalid token gets `401` with `WWW-Authenticate: Bearer error="invalid_token"`; if the
keys can't be fetched, `503`. Your handler receives the caller as `extra.authInfo`. To verify tokens
in an MCP server that doesn't run inside your app, use `verifyReintersectToken` from
`@reintersect/sdk/mcp`. See [Server handlers](/mcp/servers).

<Note>
  Reintersect-signed tokens only authenticate Reintersect's calls to your MCP server. Deliveries to
  your app's endpoint always use [webhook signatures](/webhooks-and-security), whichever `auth` you
  choose.
</Note>

## No authentication

`auth: "none"` sends requests without a token. Use it only for tools that return public data.
Nothing else in the request, including `_meta`, is authenticated, so don't use it to decide who is
calling.


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