Skip to content

Document user-scoped MCP client credentials (proposed) - #7731

Draft
yangleyland wants to merge 1 commit into
mainfrom
leyland/mcp-client-credentials-user-token
Draft

yangleyland wants to merge 1 commit into
mainfrom
leyland/mcp-client-credentials-user-token

Conversation

@yangleyland

Copy link
Copy Markdown
Contributor

Summary

Draft docs for a proposed feature: pass a user's OAuth access token (subject_token) with the MCP client_credentials grant, and Mintlify mints an /authed/mcp token scoped to that user's groups by calling the site's Info API URL. This covers the TRM Labs relay case and the Elise AI "no IdP redirect" case.

The feature is not built yet. Don't merge until it ships.

Adds a "Request access on behalf of a user" subsection under Client credentials in ai/model-context-protocol.mdx.

Research: what it would take

The current flow is the client route at mint/apps/client/src/app/_mintlify/mcp/[subdomain]/oauth/token/route.ts (handleClientCredentials) calling server POST /api/mcp/oauth/token/client-credentials and then McpOAuthService.clientCredentialsGrant. Today that path signs userInfo: { groups: credential.groups }. The /authed/mcp handler already filters by userInfo.groups on the JWT, so the MCP side needs no changes.

mint (client)

  • handleClientCredentials: read the optional subject_token form field and forward it as subjectToken. Map upstream invalid_grant errors through.

server

  • mcpClientCredentialsGrantBodySchema: add an optional subjectToken with a max length.
  • ClientCredential model and create schema: add allowUserTokens: boolean (default false).
  • clientCredentialsGrant: when subjectToken is present:
    • Reject unless credential.allowUserTokens is set and the deployment's auth is OAuth with an apiUrl.
    • fetch(auth.apiUrl, { headers: { Authorization: Bearer <subjectToken> }, redirect: 'error' }) with a timeout, then userInfoSchema.safeParse. This is the same logic as ClientAuthService.oauthHandshake, so pull it into a shared helper.
    • Use the IdP's userInfo instead of the credential's groups. issueAccessToken already caps the TTL with userInfo.expiresAt.
    • Issue only an access token, with no refresh token. refreshToken() would replay the stored userInfo, so groups would stay stale after an IdP change or revocation. The caller re-sends the user token when the access token expires (10 min TTL).
  • Rate-limit Info API URL calls per credential so the token endpoint can't be used to hammer the customer's IdP. Optionally cache the result for the access token TTL, keyed by a hash of the token.
  • Log the credential ID and the result only. Never log the subject token.

dashboard

  • Add an "Allow user tokens" toggle to create-credential-drawer.tsx / create-mcp-client-credential-dialog.tsx, and show it in credentials-section.tsx.

Out of scope for v1

  • Sites using token claims (auth.tokenClaims). Today we decodeJwt without verifying, which is fine after our own code exchange but unsafe for a token a third party hands us. Supporting it needs JWKS verification.
  • JWT and password auth. The earlier POC (leyland/mcp-token-exchange-poc) also accepted a JWT signed with the site's key. We can add that later.
  • Full RFC 8693 (grant_type=urn:ietf:params:oauth:grant-type:token-exchange). This extends client_credentials as requested, but uses the RFC's subject_token name so we can move to real token exchange later without renaming.

Open questions for TRM and Elise

  • Does their Info API URL accept the token their server holds? That token may be issued for a different audience. If not, they'd need a userinfo endpoint that does.
  • Is a 10 minute access token with no refresh OK, or do they want a longer TTL?

Checks

  • mint broken-links passes.
  • vale isn't installed in this workspace, so I didn't run it.

Created by Leyland Yang (leyland@mintlify.com) with Replicas

Replicas Workspace Slack Thread

🤖 Generated with Claude Code

Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>
@mintlify

mintlify Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
mintlify 🟢 Ready View Preview Oct 6, 2026, 6:13 AM

This branch was successfully deployed

1 active deployment
staging — 60681035 Deployed Oct 6, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant