Skip to main content

MCP OBO Auth

OAuth 2.0 On-Behalf-Of (OBO) auth lets LiteLLM exchange a user's incoming bearer token for a scoped token that is valid for a specific MCP server.

Use OBO when:

  • Your MCP server should receive a token minted specifically for that MCP server.
  • Your identity provider supports RFC 8693 OAuth 2.0 Token Exchange.
  • You want LiteLLM to keep the user's raw token from being forwarded directly to the MCP server.

How It Works​

In short:

  1. The client sends a request to LiteLLM with a bearer token.
  2. LiteLLM uses that bearer token as the RFC 8693 subject_token.
  3. LiteLLM exchanges it at your identity provider's token exchange endpoint.
  4. LiteLLM forwards only the exchanged scoped token to the MCP server.
  5. LiteLLM caches the exchanged token until it expires, so repeated calls avoid another identity provider round trip.

Configure an MCP Server for OBO​

Set auth_type: oauth2_token_exchange on the MCP server.

config.yaml
mcp_servers:
internal_tools:
url: "https://mcp.example.com/mcp"
transport: "http"
auth_type: oauth2_token_exchange

# OAuth 2.0 Token Exchange endpoint on your identity provider
token_exchange_endpoint: "https://idp.example.com/oauth2/token"

# Token exchange client registered with your identity provider
client_id: "<idp-client-id>"
client_secret: "<idp-client-secret>"

# Optional but recommended: restrict the exchanged token to this MCP server
audience: "api://internal-tools-mcp"
scopes:
- "mcp.tools.read"
- "mcp.tools.execute"

# Optional. Defaults to access_token.
subject_token_type: "urn:ietf:params:oauth:token-type:access_token"

Config Fields​

FieldRequiredDescription
auth_typeYesMust be oauth2_token_exchange.
token_exchange_endpointYesThe identity provider endpoint that accepts RFC 8693 token exchange requests.
client_idYesOAuth client identifier LiteLLM uses when calling the token exchange endpoint.
client_secretYesOAuth client secret LiteLLM uses when calling the token exchange endpoint.
audienceRecommendedResource identifier for the MCP server. LiteLLM sends this as the token exchange audience.
scopesOptionalScopes LiteLLM requests for the exchanged token. LiteLLM joins the list into the OAuth scope parameter.
subject_token_typeOptionalRFC 8693 subject token type. Defaults to urn:ietf:params:oauth:token-type:access_token.

Token Exchange Request​

For each uncached subject token and MCP server pair, LiteLLM sends a form-encoded request like this to token_exchange_endpoint:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<caller-bearer-token>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&client_id=<idp-client-id>
&client_secret=<idp-client-secret>
&audience=api://internal-tools-mcp
&scope=mcp.tools.read mcp.tools.execute

Your identity provider should return an access token:

{
"access_token": "scoped-token-for-mcp-server",
"token_type": "Bearer",
"expires_in": 3600
}

LiteLLM then calls the MCP server with:

Authorization: Bearer scoped-token-for-mcp-server

Calling an OBO MCP Server​

The inbound request must include the user's bearer token so LiteLLM has a subject_token to exchange.

For direct MCP calls, keep the LiteLLM key in x-litellm-api-key and leave Authorization for the user token:

Direct MCP call
curl -X POST "https://litellm.example.com/internal_tools/mcp" \
-H "Content-Type: application/json" \
-H "x-litellm-api-key: Bearer <litellm-api-key>" \
-H "Authorization: Bearer <user-token>" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

For the Responses API, pass MCP tool headers with the LiteLLM key separated from the user token:

Responses API with MCP OBO
curl -X POST "https://litellm.example.com/v1/responses" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <litellm-api-key>" \
-d '{
"model": "gpt-4o",
"input": "List the available internal tools",
"tools": [
{
"type": "mcp",
"server_label": "internal_tools",
"server_url": "https://litellm.example.com/internal_tools/mcp",
"require_approval": "never",
"headers": {
"x-litellm-api-key": "Bearer <litellm-api-key>",
"Authorization": "Bearer <user-token>"
}
}
]
}'
tip

If the MCP client can only send one Authorization header, use x-litellm-api-key for the LiteLLM key and reserve Authorization for the user's token. LiteLLM needs the user token as the OBO subject_token.

Caching Behavior​

LiteLLM caches exchanged tokens by:

  • subject token
  • MCP server ID

This means two different users get separate exchanged tokens, while repeated calls from the same user to the same MCP server reuse the cached token until it expires.

The cache TTL is based on expires_in minus LiteLLM's OAuth expiry buffer. If expires_in is missing or invalid, LiteLLM uses the default OAuth token cache TTL.

Fallback Behavior​

If an OBO server has no incoming subject token:

  • If client_id, client_secret, and token_url are configured, LiteLLM can fall back to OAuth client_credentials.
  • Otherwise, LiteLLM logs a warning and proceeds without token exchange.

For strict OBO deployments, configure clients so every request includes the user bearer token.

Troubleshooting​

SymptomCheck
MCP server receives the LiteLLM keyMove the LiteLLM key to x-litellm-api-key and use Authorization for the user token.
Token exchange endpoint returns 400Confirm audience, scopes, client_id, and subject_token_type match your identity provider configuration.
MCP server receives no Authorization headerConfirm the MCP server has auth_type: oauth2_token_exchange and the inbound request includes a user bearer token.
Identity provider is called on every requestConfirm the identity provider returns expires_in, and that the same user token and MCP server are being reused.
🚅
LiteLLM Enterprise
SSO/SAML, audit logs, spend tracking, multi-team management, and guardrails — built for production.
Learn more →