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

# User Context

> Use verified OAuth identity inside MCP server callbacks.

On a server configured with `oauth`, tool, resource, and prompt callbacks receive verified authentication data on `ctx.auth`. The provider determines the static type of `ctx.auth.user`; there is no generic `UserInfo` shape.

## Auth shape

Every provider exposes this top-level shape:

```typescript theme={null}
type OAuthAuth<TUser> = {
  user: TUser;
  payload: Record<string, unknown>;
  accessToken: string;
  scopes: string[];
  permissions: string[];
  clientId?: string;
  expiresAt: number;
  resource?: URL;
  upstream?: { accessToken: string; scopes: string[]; expiresAt?: number };
};
```

* `user` is the provider's normalized user type.
* `payload` contains the verified token claims or introspection data.
* `accessToken` is the verified bearer token. Forward it only when calling an upstream API on behalf of the user. On an [OAuth Proxy](/v2/typescript/server/authentication/providers/oauth-proxy) server it is the proxy's own token, which upstream APIs do not accept.
* `scopes` are copied from the auth information returned by the token verifier.
* `permissions` are produced by the provider's mapping.
* `clientId` is present when the token has a client identifier.
* `expiresAt` is a Unix timestamp in seconds.
* `resource` is the protected resource validated by the verifier.
* `upstream` is set only on an OAuth Proxy server. It holds the upstream provider's token for the user; read it with `upstreamToken(ctx.auth)` from `mcp-use/oauth`.

## Use provider-specific fields

All built-in users have `id`. Optional profile and organization fields vary:

| Provider | Selected normalized fields |
| - | - |
| Auth0 | `id`, `email`, `name`, `nickname`, `picture`, `emailVerified`, `updatedAt`, `roles` |
| Clerk | `id`, `email`, `name`, `username`, `organizationId`, `organizationRole`, `organizationSlug`, `roles` |
| Keycloak | `id`, `email`, `name`, `preferredUsername`, `givenName`, `familyName`, `roles`, `realmAccess`, `resourceAccess` |
| Supabase | `id`, `email`, `name`, `fullName`, `username`, `avatarUrl`, `role`, `aal`, `amr`, `sessionId` |
| WorkOS | `id`, `email`, `name`, `preferredUsername`, `firstName`, `lastName`, `roles`, `organizationId`, `sessionId` |

The OAuth Proxy is the exception: its user is `{ subject, profile }`, where `subject` is the provider's stable user ID and `profile` holds the provider's fields. Pass `mapUser` to `oauthProxy` to return a user with `id`.

```typescript theme={null}
server.tool(
  {
    name: "list_documents",
    description: "List documents for the caller's Clerk organization.",
  },
  async (_args, ctx) => {
    const organizationId = ctx.auth.user.organizationId;
    if (!organizationId) {
      return {
        isError: true,
        content: [{ type: "text", text: "Organization context required" }],
      };
    }

    const documents = await db.documents.findMany({
      where: { organizationId },
    });
    return {
      content: [{ type: "text", text: JSON.stringify(documents) }],
    };
  },
);
```

Use normalized fields for application logic. Read `payload` only when you deliberately need a provider claim that is not mapped.

## Check scopes and permissions

Scopes describe OAuth grants. Permissions describe provider-mapped application authorization.

```typescript theme={null}
import { z } from "zod";

server.tool(
  {
    name: "delete_document",
    description: "Delete a document when the caller has permission.",
    inputSchema: z.object({ documentId: z.string() }),
  },
  async ({ documentId }, ctx) => {
    if (!ctx.auth.permissions.includes("documents:delete")) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: "Forbidden: documents:delete permission required",
          },
        ],
      };
    }

    await db.documents.delete({ id: documentId });
    return { content: [{ type: "text", text: "Document deleted" }] };
  },
);
```

Provider mappings differ: Auth0 and WorkOS map their permission claim, Clerk maps organization permissions, Keycloak flattens resource roles as `client:role`, and Supabase maps AAL to `aal:<level>`.

## Map custom claims

For another authorization server, `oauthCustomProvider` separates verification from application mapping:

```typescript theme={null}
import { MCPServer } from "mcp-use";
import {
  OAuthError,
  OAuthErrorCode,
  oauthCustomProvider,
  type OAuthAuthInfo,
  type OAuthMetadata,
} from "mcp-use/oauth";

type AppUser = {
  id: string;
  email?: string;
  organizationId?: string;
};

const demoAccessToken = process.env.DEMO_ACCESS_TOKEN;
if (!demoAccessToken) throw new Error("DEMO_ACCESS_TOKEN is required");

const oauthMetadata = {
  issuer: "https://auth.example.com",
  authorization_endpoint: "https://auth.example.com/oauth/authorize",
  token_endpoint: "https://auth.example.com/oauth/token",
  registration_endpoint: "https://auth.example.com/oauth/register",
  response_types_supported: ["code"],
  code_challenge_methods_supported: ["S256"],
} satisfies OAuthMetadata;

const server = new MCPServer({
  name: "custom-auth-server",
  version: "1.0.0",
  oauth: oauthCustomProvider<AppUser>({
    oauthMetadata,
    createTokenVerifier: (resource) => ({
      async verifyAccessToken(token) {
        if (token !== demoAccessToken) {
          throw new OAuthError(
            OAuthErrorCode.InvalidToken,
            "Invalid access token",
          );
        }

        return {
          token,
          clientId: "demo-client",
          scopes: ["profile"],
          expiresAt: Math.floor(Date.now() / 1000) + 3600,
          resource,
          extra: {
            payload: {
              sub: "user-123",
              email: "user@example.com",
              organization_id: "org-123",
              permissions: ["documents:read"],
            },
          },
        };
      },
    }),
    mapAuthInfo(authInfo: OAuthAuthInfo) {
      const payload = authInfo.extra?.payload as Record<string, unknown>;
      return {
        user: {
          id: String(payload.sub),
          email: typeof payload.email === "string" ? payload.email : undefined,
          organizationId:
            typeof payload.organization_id === "string"
              ? payload.organization_id
              : undefined,
        },
        payload,
        permissions: Array.isArray(payload.permissions)
          ? payload.permissions.filter(
              (value): value is string => typeof value === "string",
            )
          : [],
      };
    },
  }),
});
```

This fixed-token verifier keeps the example small and is only suitable for local testing. In production, validate the token signature, issuer, expiry, and MCP resource, or introspect an opaque token with the authorization server.

`createTokenVerifier` receives the canonical MCP resource. The verifier rejects a token by throwing `OAuthError` with `OAuthErrorCode.InvalidToken`, so the client gets `401` with a `WWW-Authenticate` challenge, or, for a ChatGPT `tools/call` request, an `isError` tool result carrying the same challenge (see [Mixed auth](/v2/typescript/server/authentication/mixed-auth)). An error that is not an `OAuthError` is answered with `500`. The auth information it returns must carry `resource` as a `URL` matching that resource, `clientId` as a string (`""` when the token has no client), and `expiresAt` in Unix seconds in the future. `mapAuthInfo` runs only after SDK auth information has been verified and must return `{ user, payload, permissions }`. It does not rewrite `ctx.auth.scopes`; those come from the verifier's verified auth info.

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="shield-check" href="/v2/typescript/server/authentication/index">
    Choose and configure an OAuth provider.
  </Card>

  <Card title="Custom Provider" icon="code" href="/v2/typescript/server/authentication/providers/custom">
    Implement resource-bound token verification and claim mapping.
  </Card>

  <Card title="Middleware" icon="layers" href="/v2/typescript/server/middleware">
    Apply shared authorization checks.
  </Card>
</CardGroup>


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