MCP OAuth
MCP OAuth
LiteLLM은 MCP 서버에 대한 여러 OAuth 2.0 패턴을 지원해요. config.yaml의 모든 auth_type: oauth2 서버는 oauth2_flow로 흐름을 선언해야 해요. 패스스루 모드는 자체 auth_type 값이며 MCP OAuth Passthrough에 문서화돼 있어요:
| 흐름 | oauth2_flow |
사용 사례 | 동작 방식 |
|---|---|---|---|
| Interactive (PKCE) | authorization_code |
사용자 대면 앱(Claude Code, Cursor) | 브라우저 기반 동의, 사용자별 토큰 |
| Machine-to-Machine (M2M) | client_credentials |
백엔드 서비스, CI/CD, 자동 에이전트 | client_credentials 그랜트, 프록시 관리 토큰 |
| On-Behalf-Of (OBO) | n/a(auth_type: oauth2_token_exchange 사용) |
보호된 MCP 서버에 대한 사용자 컨텍스트 도구 호출 | LiteLLM이 호출자 토큰을 스코프된 MCP 토큰으로 교환. MCP OBO Auth 참고 |
| Passthrough (transparent) | n/a(auth_type: true_passthrough 사용) |
클라이언트가 이미 업스트림 토큰 보유; LiteLLM은 자체 인증 추가 안 함 | 클라이언트의 Authorization 그대로 전달, LiteLLM 입장 없음. MCP OAuth Passthrough 참고 |
| 위임된 업스트림 OAuth | n/a(auth_type: oauth_delegate 사용) |
LiteLLM이 호출자를 입장시키고 업스트림이 도구 권한 소유 | LiteLLM 입장 + 별도 전달된 업스트림 베어러, 지출·속도 제한 유지. MCP OAuth Passthrough 참고 |
출처: 문서
본문
Interactive OAuth (PKCE)
사용자 대면 MCP 클라이언트(Claude Code, Cursor)에는 LiteLLM이 PKCE가 포함된 전체 OAuth 2.0 authorization code 흐름을 지원해요.
설정 (Setup)
config.yaml:
mcp_servers:
github_mcp:
url: "https://api.githubcopilot.com/mcp"
auth_type: oauth2
oauth2_flow: authorization_code
client_id: os.environ/GITHUB_OAUTH_CLIENT_ID
client_secret: os.environ/GITHUB_OAUTH_CLIENT_SECRET
동작 방식 (How It Works)
참여자 (Participants)
- Client — 사용자를 대신해 OAuth 디스커버리, 인증, 도구 호출을 시작하는 MCP 가능 AI 에이전트(예: Claude Code, Cursor 또는 다른 IDE/에이전트).
- LiteLLM Proxy — 저장된 자격 증명을 보호하면서 모든 OAuth 디스커버리, 등록, 토큰 교환, MCP 트래픽을 중재.
- Authorization Server — 동적 클라이언트 등록, PKCE 인증, 토큰 엔드포인트로 OAuth 2.0 토큰 발행.
- MCP Server (Resource Server) — LiteLLM의 인증된 JSON-RPC 요청을 받는 보호된 MCP 엔드포인트.
- User-Agent (Browser) — 인증 단계에서 최종 사용자가 동의를 부여하도록 일시적으로 관여.
흐름 단계 (Flow Steps)
- 리소스 디스커버리: 클라이언트가 LiteLLM의
.well-known/oauth-protected-resource엔드포인트에서 MCP 리소스 메타데이터를 가져와 스코프와 기능을 이해. - Authorization Server 디스커버리: 클라이언트가 LiteLLM의
.well-known/oauth-authorization-server엔드포인트를 통해 OAuth 서버 메타데이터(토큰 엔드포인트, 인증 엔드포인트, 지원 PKCE 메서드)를 검색. - Dynamic Client Registration: 클라이언트가 LiteLLM을 통해 등록하며, LiteLLM은 요청을 authorization server로 전달(RFC 7591). 프로바이더가 동적 등록을 지원하지 않으면 LiteLLM에
client_id/client_secret을 미리 저장(예: GitHub MCP)하고 흐름은 같은 방식으로 진행. - 사용자 인증: 클라이언트가 브라우저 세션(code challenge와 리소스 힌트 포함)을 시작. 사용자가 접근을 승인하고 authorization server가 code를 LiteLLM을 통해 클라이언트로 보냄.
- 토큰 교환: 클라이언트가 authorization code, code verifier, 리소스로 LiteLLM을 호출. LiteLLM이 authorization server와 교환하고 발행된 access/refresh 토큰을 반환.
- MCP 호출: 유효한 토큰으로 클라이언트가 MCP JSON-RPC 요청(플러스 LiteLLM API 키)을 LiteLLM에 보내고, LiteLLM이 그것을 MCP 서버로 전달하며 도구 응답을 중계.
추가 참조: 공식 MCP Authorization Flow.
정적 OAuth 클라이언트의 Redirect URL
업스트림 ID 공급자(IdP)가 애플리케이션을 사전 등록하도록 요구할 때 정적 OAuth 클라이언트를 사용하세요. LiteLLM MCP 서버 항목에 애플리케이션의 client_id와 client_secret을 구성하세요. Dynamic Client Registration(RFC 7591)을 제공하지 않는 프로바이더를 지원해요.
인증 흐름은 두 콜백 URL을 사용해요:
| 콜백 | 용도 | 구성 |
|---|---|---|
| LiteLLM 콜백 | 업스트림 IdP의 인증 응답을 받음 | IdP 애플리케이션의 Redirect URI 또는 Callback URL 필드에 <proxy origin>/callback 등록 |
| MCP 클라이언트 콜백 | LiteLLM의 인증 응답을 MCP 클라이언트로 반환 | 클라이언트가 이 URL을 /{mcp_server_name}/authorize에 redirect_uri로 제공. 아래 검증 규칙이 요구할 때 LiteLLM에서 추가 신뢰 콜백 구성 |
예를 들어 https://llm.example.com의 프록시는 https://llm.example.com/callback을 IdP 콜백으로 사용해요. http://localhost:33418/callback 같은 데스크톱 클라이언트의 로컬 콜백은 LiteLLM에 제공되며 업스트림 IdP에 등록할 필요가 없어요.
업스트림 OAuth 애플리케이션 구성
PROXY_BASE_URL을 프록시의 공용 origin으로 설정하고 해당 /callback URL을 IdP에 등록하세요:
export PROXY_BASE_URL=https://llm.example.com
# IdP callback URL: https://llm.example.com/callback
LiteLLM은 PROXY_BASE_URL, 신뢰되는 X-Forwarded-* 헤더, 들어오는 요청 URL 순서로 공용 origin을 해석해요. 헤더 신뢰 요구사항은 Reverse proxy and ingress configuration 참고.
MCP 서버 구성에 애플리케이션의 자격 증명과 OAuth 엔드포인트를 추가하세요:
config.yaml:
mcp_servers:
jira_mcp:
url: https://mcp.example.com/mcp
auth_type: oauth2
oauth2_flow: authorization_code
client_id: os.environ/JIRA_OAUTH_CLIENT_ID
client_secret: os.environ/JIRA_OAUTH_CLIENT_SECRET
authorization_url: https://idp.example.com/oauth2/authorize
token_url: https://idp.example.com/oauth2/token
scopes:
- read:jira-work
예시 서버 URL, OAuth 엔드포인트, 스코프를 프로바이더 값으로 바꾸세요. authorization_url과 token_url은 업스트림 MCP 서버가 LiteLLM이 디스커버리할 수 있는 OAuth 메타데이터를 게시할 때 선택이에요. 명시적으로 구성된 엔드포인트가 충돌하는 발견 엔드포인트보다 우선해요.
정적 클라이언트의 경우 LiteLLM이 POST /{mcp_server_name}/register을 로컬로 처리해요. MCP 서버 이름을 client_id로, dummy를 client_secret으로, 클라이언트가 제출한 redirect_uris를 반환해요. LiteLLM은 인증·토큰 교환에 구성된 업스트림 자격 증명을 사용해요.
MCP 클라이언트의 콜백 URL 식별
POST /{mcp_server_name}/register의 redirect_uris 필드 또는 GET /{mcp_server_name}/authorize의 redirect_uri 파라미터에서 콜백 URL을 얻으세요. 콜백 경로·포트는 클라이언트 버전과 배포에 따라 다를 수 있어요.
origin 불일치 시 HTTP 400 응답이 제출된 URL을 detail.redirect_uri에 포함해요. 프록시는 거부된 값을 MCP OAuth: rejecting redirect_uri '<value>'로 경고 수준에 로깅해요.
다음 예시들이 일반적인 콜백 구성을 요약해요:
| 클라이언트 또는 콜백 유형 | 콜백 예시 | LiteLLM 구성 |
|---|---|---|
| Cursor | cursor://anysphere.cursor-mcp/oauth/callback |
내장 신뢰 콜백에 포함. |
| 루프백 리스너를 사용하는 데스크톱·명령줄 클라이언트 | http://localhost:33418/callback |
루프백 콜백은 모든 포트에서 허용. |
| VS Code for the Web | https://vscode.dev/redirect 또는 https://insiders.vscode.dev/redirect |
MCP_TRUSTED_REDIRECT_ORIGINS에 vscode.dev,insiders.vscode.dev 추가 |
| 별도 origin의 웹 애플리케이션 | https://app.example.com/oauth/callback |
MCP_TRUSTED_REDIRECT_ORIGINS에 app.example.com 추가 |
| 사용자 지정 URI 스킴을 사용하는 네이티브 클라이언트 | myclient://auth/callback |
콜백 URI를 MCP_TRUSTED_NATIVE_REDIRECT_URIS에 추가 |
| LiteLLM Admin UI | <proxy origin>/ui/mcp/oauth/callback |
프록시의 해석된 공용 origin이 UI origin과 일치하면 허용. |
추가 신뢰 콜백이 필요한 클라이언트에는 프록시 배포에서 해당 환경 변수를 설정하세요:
# Trusted HTTPS client hosts, with optional ports or wildcard subdomains.
export MCP_TRUSTED_REDIRECT_ORIGINS='app.example.com,*.tools.example.com'
# Trusted native client callback URIs.
export MCP_TRUSTED_NATIVE_REDIRECT_URIS='myclient://auth/callback'
MCP_TRUSTED_REDIRECT_ORIGINS는 호스트 또는 host:port 항목의 쉼표 구분 목록을 받아요. MCP_TRUSTED_NATIVE_REDIRECT_URIS는 네이티브 콜백 URI의 쉼표 구분 목록을 받아요. 콜백 경로를 포함하세요. myclient://auth/callback과 myclient://auth/callback/은 별개 항목이에요.
Redirect URI 검증
서버별 정적 OAuth에서 /{mcp_server_name}/authorize는 다음 규칙으로 콜백을 검증해요:
| 콜백 유형 | 요구사항 |
|---|---|
| 신뢰 네이티브 콜백 | 내장 Cursor 콜백 또는 MCP_TRUSTED_NATIVE_REDIRECT_URIS의 항목과 일치. |
| 루프백 | localhost, 127.0.0.0/8의 주소, 또는 ::1과 함께 HTTP 또는 HTTPS 사용. 모든 포트·경로 허용. |
| 동일 origin | 프록시의 해석된 공용 origin과 같은 스킴·호스트·포트 사용. 기본 포트는 정규화됨. |
| 추가 신뢰 origin | HTTPS와 MCP_TRUSTED_REDIRECT_ORIGINS에 나열된 호스트 또는 host:port 사용. *.tools.example.com 같은 와일드카드는 a.tools.example.com을 포함한 하위 도메인을 매칭하지만 tools.example.com 자체는 제외. |
콜백 URL은 호스트를 포함해야 하고 프래그먼트(#...), 내장 자격 증명(user:pass@host), 호스트의 백슬래시를 포함해서는 안 돼요. 사용자 지정 URI 스킴은 신뢰 네이티브 콜백 항목이 필요해요. 네이티브 콜백 URI는 쿼리 문자열을 포함해서는 안 돼요. HTTP·HTTPS 콜백은 쿼리 문자열을 포함할 수 있으며 LiteLLM이 클라이언트로 리다이렉트할 때 보존해요.
콜백 오류 문제 해결
LiteLLM이 서버별 콜백을 거부하면 detail.error가 invalid_request로 설정된 HTTP 400을 반환해요. detail.error_description이 검증 실패를 식별해요. 일부 응답은 지침과 함께 detail.hint를, origin 불일치 응답은 detail.redirect_uri를 포함해요.
| 오류 또는 증상 | 해결책 |
|---|---|
| 업스트림 IdP가 redirect URI 불일치 보고 | IdP 앱의 등록 콜백이 <proxy origin>/callback이고 LiteLLM이 예상 공용 origin을 해석하는지 확인. |
| 프록시 공용 origin의 콜백을 LiteLLM이 거부 | PROXY_BASE_URL 설정 또는 신뢰되는 전달 헤더 구성. Reverse proxy and ingress configuration 참고. |
| 별도 origin의 HTTPS 콜백을 LiteLLM이 거부 | 해당될 때 포트 포함 승인된 클라이언트 호스트를 MCP_TRUSTED_REDIRECT_ORIGINS에 추가. |
| 사용자 지정 URI 스킴을 LiteLLM이 거부 | 클라이언트의 콜백 URI를 MCP_TRUSTED_NATIVE_REDIRECT_URIS에 추가. |
| LiteLLM이 콜백에 URL 프래그먼트가 있다고 보고 | 클라이언트가 프래그먼트 없는 콜백 URL을 사용하도록 구성. |
예를 들어 origin 불일치 응답에는 다음 필드가 있어요:
{
"detail": {
"error": "invalid_request",
"error_description": "redirect_uri origin (https://app.example.com) does not match the proxy origin. host/port: redirect_uri 'app.example.com' does not match the proxy origin",
"redirect_uri": "https://app.example.com/oauth/callback"
}
}
게이트웨이 Dynamic Client Registration
집계 /mcp 엔드포인트는 POST /register, GET /authorize, POST /token을 통해 게이트웨이 레벨 등록을 사용해요. 각 등록은 하나에서 네 개까지 redirect_uris를 받으며 URI당 최대 256자예요.
이 흐름에서 /authorize에 제공된 redirect_uri는 등록된 값과 정확히 일치해야 해요. 등록되지 않은 값은 다음 최상위 JSON 필드와 함께 HTTP 400을 반환해요:
{
"error": "invalid_request",
"error_description": "redirect_uri is not registered for this client"
}
서버별 정적 등록은 플레이스홀더 자격 증명을 반환하고 클라이언트별 콜백 허용 목록을 저장하지 않아요. 그 인증 엔드포인트는 위에서 설명한 서버별 검증 규칙을 적용해요.
구성 검증
다음 요청으로 디스커버리, 정적 등록, 인증 리다이렉트를 검증해 주세요. 예시는 http://localhost:4000에서 PROXY_BASE_URL=https://llm.example.com과 위 jira_mcp 구성을 가진 프록시를 사용해요.
authorization server 메타데이터 검색:
curl -sS http://localhost:4000/.well-known/oauth-authorization-server/jira_mcp | jq .issuer
# "https://llm.example.com/jira_mcp"
issuer의 origin은 https://llm.example.com이므로 IdP 콜백 URL은 https://llm.example.com/callback이에요.
정적 등록 응답 검증:
curl -sS -X POST http://localhost:4000/jira_mcp/register \
-H 'Content-Type: application/json' \
-d '{"client_name":"my-mcp-client","redirect_uris":["http://localhost:33418/callback"]}'
# {"client_id":"jira_mcp","client_secret":"dummy","redirect_uris":["http://localhost:33418/callback"]}
루프백 콜백으로 인증 요청:
curl -sS -o /dev/null -D - --get http://localhost:4000/jira_mcp/authorize \
--data-urlencode 'response_type=code' \
--data-urlencode 'client_id=jira_mcp' \
--data-urlencode 'redirect_uri=http://localhost:33418/callback' \
--data-urlencode 'state=example-state' \
--data-urlencode 'code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM' \
--data-urlencode 'code_challenge_method=S256'
예상 응답은 HTTP 307 Temporary Redirect예요. Location 헤더는 업스트림 인증 엔드포인트를 가리키고 구성된 업스트림 client_id와 URL 인코딩된 redirect_uri=https://llm.example.com/callback을 포함해요.
추가 신뢰 origin을 검증하려면 redirect_uri=https://app.example.com/oauth/callback으로 인증 요청을 반복하세요. 일치하는 MCP_TRUSTED_REDIRECT_ORIGINS 항목이 없으면 예상 응답은 HTTP 400이에요. MCP_TRUSTED_REDIRECT_ORIGINS=app.example.com을 설정하고 프록시를 다시 시작한 후 반복하면 예상 응답은 HTTP 307이에요.
리버스 프록시와 인그레스 구성
LiteLLM이 TLS 종료 인그레스(Kubernetes, ALB, nginx, Cloudflare 등) 뒤에서 실행되면 프록시가 공용 origin을 알아야 OAuth authorize 엔드포인트가 브라우저 제공 redirect_uri(예: https://llm.example.com/ui/mcp/oauth/callback)를 자체 스킴+호스트+포트와 비교할 수 있어요. 프록시가 내부 주소(http://<pod-ip>:4000)로 해석되면 동일 origin 검사가 실패하고 MCP 서버 페이지의 Connect 버튼이 400 Bad Request와 {"detail":"invalid_request"}를 반환해요.
가장 간단하고 권장되는 수정은 PROXY_BASE_URL을 사용자가 주소창에서 보는 정확한 origin으로 설정하는 거예요:
PROXY_BASE_URL=https://llm.example.com
값 규칙:
- 전체 origin만: 스킴 + 호스트(+ 비기본 포트).
- 후행 슬래시, 경로 구성 요소 없음.
- 주소창과 정확히 일치해야 함.
https://llm.example.com과https://llm.example.com:443은 같은 origin으로 받아들여지지만(기본 포트 정규화),https://llm.example.com은https://llm.example.com:8443에 대해 실행 중인 브라우저와 일치하지 않아요.
PROXY_BASE_URL이 설정되면 LiteLLM이 그것을 직접 사용하고 아래 설명할 X-Forwarded-* 신뢰 경로를 건너뛰어요.
Origin 해석 순서
MCP OAuth 엔드포인트에 대해 LiteLLM은 프록시의 공용 origin을 다음 순서로 해석해요:
PROXY_BASE_URLenv var — 유효한http(s)URL로 설정되면 그대로 사용. 잘못된 값은 경고와 함께 무시.X-Forwarded-Proto/X-Forwarded-Host/X-Forwarded-Port— 두 가지 모두use_x_forwarded_for가true이고mcp_trusted_proxy_ranges안에 요청 피어 IP가 있을 때만 인정.use_x_forwarded_for가mcp_trusted_proxy_ranges없이 활성화되면 헤더는 신뢰되지 않음(신뢰 리버스 프록시와 직접 공격자를 구분할 방법이 없음).request.base_url— FastAPI가 요청에서 보는 리터럴 URL. 인그레스 배포에서 보통http://<internal-host>:4000이고 브라우저 origin과 일치하지 않음.
PROXY_BASE_URL을 설정할 수 없거나 원하지 않으면 X-Forwarded 경로를 명시적으로 구성하세요:
config.yaml:
general_settings:
use_x_forwarded_for: true
mcp_trusted_proxy_ranges:
- "10.0.0.0/8" # your ingress / load-balancer CIDR(s)
그리고 인그레스가 X-Forwarded-Proto, X-Forwarded-Host, (비기본이면) X-Forwarded-Port를 보내는지 검증하세요. 진단 curl은 MCP OAuth troubleshooting 참고.
추가 일자리(First-party) redirect_uri origins 허용
일자리(First-party) OAuth 클라이언트가 자매 도메인(예: llm.example.com의 MCP 프록시에 대해 등록하는 app.example.com의 내부 웹 앱)에 있다면, 프록시 자신의 것에 더해 그 origin을 허용 목록에 넣으려면 MCP_TRUSTED_REDIRECT_ORIGINS를 설정하세요:
MCP_TRUSTED_REDIRECT_ORIGINS=app.example.com,*.tools.example.com
host또는host:port항목의 쉼표 구분 목록.- HTTPS만. 허용 목록 경로는 비-
httpsredirect_uri를 거부. *.suffix항목은suffix의 엄격히 더 깊은 하위 도메인만 매칭(*.tools.example.com은a.tools.example.com매칭,tools.example.com은 안 됨).- 루프백(
localhost,127.0.0.0/8,::1)은 이 설정과 무관하게 항상 허용.
이것은 통제하는 일자리(First-party) OAuth 클라이언트용이에요. 표준 인그레스 경우에는 PROXY_BASE_URL을 선호하세요.
동일 origin 검사가 존재하는 이유
MCP 프록시의 /v1/mcp/server/oauth/<server_id>/authorize 엔드포인트는 호출자 redirect_uri가 프록시 자신의 공용 origin(또는 위 루프백/허용 목록 항목 중 하나)과 스킴+호스트+포트를 공유하는지 검증해요. 이 검사는 공격자가 로그인한 관리자를, GitHub·Slack 같은 업스트림 OAuth 보호 MCP 서버의 인증 코드를 공격자 제어 호스트를 통해 바운스시키는 링크로 피싱하는 것을 막기 위해 존재해요. 동일 origin(플러스 명시적 운영 허용 목록)은 네이티브 MCP 클라이언트에 쓰이는 루프백 전용 규칙의 위협 모델 안전 버전이에요.
PROXY_BASE_URL이 인그레스 배포의 올바른 탈출구인 이유는 운영자가 프록시의 실제 공용 origin을 대역 외로 선언하고, 공격자가 설정할 수 있는 헤더에서 추론하도록 프록시에 요청하지 않기 때문이에요. 검사 자체는 완화되지 않아요.
Machine-to-Machine (M2M) 인증
LiteLLM은 client_credentials 그랜트로 OAuth2 토큰을 자동으로 가져오고·캐시하고·새로고침해요. 수동 토큰 관리가 필요 없어요.
설정 (Setup)
LiteLLM UI 또는 config.yaml로 M2M OAuth를 구성할 수 있어요.
UI 설정:
MCP Servers 페이지로 이동해 + Add New MCP Server 클릭. 서버 이름을 입력하고 전송 타입으로 HTTP 선택. MCP 서버 URL 붙여넣기. Authentication 아래에서 OAuth 선택. OAuth 흐름 타입으로 Machine-to-Machine (M2M) 선택. 이는 client_credentials 그랜트로 브라우저 상호작용 없이 서버 간 인증하는 것입니다. OAuth 프로바이더가 제공한 Client ID와 Client Secret 입력. Token URL 입력(LiteLLM이 client_credentials로 액세스 토큰을 가져올 엔드포인트). 아래로 스크롤해 서버 URL과 모든 필드를 검토하고 Create MCP Server 클릭. 생성 후 서버를 열어 MCP Tools 탭으로 가서 LiteLLM이 연결하고 사용 가능한 도구를 나열할 수 있는지 확인. 도구(예: echo)를 선택해 테스트하고 필수 파라미터를 채운 후 Call Tool 클릭. LiteLLM이 백그라운드에서 OAuth 토큰을 자동으로 가져와 도구를 호출해요. 결과가 M2M OAuth 흐름이 종단 간 작동함을 확인해요.
config.yaml 설정:
mcp_servers:
my_mcp_server:
url: "https://my-mcp-server.com/mcp"
auth_type: oauth2
oauth2_flow: client_credentials
client_id: os.environ/MCP_CLIENT_ID
client_secret: os.environ/MCP_CLIENT_SECRET
token_url: "https://auth.example.com/oauth/token"
scopes: ["mcp:read", "mcp:write"] # optional
다른 헤더로 토큰 보내기
기본적으로 LiteLLM이 해석한 토큰은 거의 모든 MCP 서버가 기대하는 Authorization: Bearer ***로 나가요. 일부 배포는 MCP 서버를 자체 자격 증명을 사설 헤더에서 읽는 API 게이트웨이 뒤에 두며, 게이트웨이 뒤의 서버는 여전히 Authorization`에 자체 베어러를 원해요. 그건 같은 요청에 두 자격 증명이 필요해요.
upstream_token_header를 설정해 해석된 토큰이 사용할 헤더를 지정하세요. 그렇게 하면 static_headers 아래에 구성한 것은 그대로 남아, 두 번째 자격 증명이 게이트웨이 뒤의 서버에 손대지 않고 도달해요.
config.yaml:
mcp_servers:
my_mcp_server:
url: "https://my-mcp-server.com/mcp"
auth_type: oauth2
oauth2_flow: client_credentials
client_id: os.environ/MCP_CLIENT_ID
client_secret: os.environ/MCP_CLIENT_SECRET
token_url: "https://auth.example.com/oauth/token"
upstream_token_header: "esb-oauth"
static_headers:
Authorization: "Bearer os.environ/UPSTREAM_MCP_TOKEN"
그러면 MCP 서버에 대한 각 요청이 둘 다를 담아요:
esb-oauth: Bearer <the token LiteLLM minted>
Authorization: Bearer *** token you configured>
upstream_token_header를 설정하지 않으면 기본값이 유지되므로 기존 서버가 영향받지 않아요. 값은 유효한 HTTP 헤더 이름이어야 해요. 프록시는 잘못된 값으로 시작을 거부하고 관리 API는 400으로 거부해요.
UI에서 같은 설정은 MCP 서버 폼의 OAuth 섹션 Token Header 필드이며, interactive 흐름과 토큰 교환 모드에도 M2M처럼 적용돼요.
동작 방식 (How It Works)
- 첫 MCP 요청에서 LiteLLM이
token_url에grant_type=client_credentials로 POST. - 액세스 토큰이 인메모리 캐시, TTL =
expires_in - 60s. - 이후 요청은 캐시된 토큰 재사용.
- 토큰이 만료되면 LiteLLM이 자동으로 새 것 가져오기.
Mock 서버로 테스트
로컬 테스트에 BerriAI/mock-oauth2-mcp-server 사용:
터미널 1 — mock 서버 시작:
uv add fastapi uvicorn
python mock_oauth2_mcp_server.py # starts on :8765
config.yaml:
mcp_servers:
test_oauth2:
url: "http://localhost:8765/mcp"
auth_type: oauth2
oauth2_flow: client_credentials
client_id: "test-client"
client_secret: "test-secret"
token_url: "http://localhost:8765/oauth/token"
터미널 2 — 프록시 시작 후 테스트:
litellm --config config.yaml --port 4000
# See MCP REST API guide for full examples (server_id, tool naming, common errors)
# https://docs.litellm.ai/docs/mcp_rest_api
curl http://localhost:4000/mcp-rest/tools/list \
-H "Authorization: Bearer ***"
curl http://localhost:4000/mcp-rest/tools/call \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"server_id": "test_oauth2",
"name": "echo",
"arguments": {"message": "hello"}
}'
구성 참조 (Config Reference)
| 필드 | 필수 | 설명 |
|---|---|---|
auth_type |
예 | oauth2여야 함. RFC 8693 On-Behalf-Of는 oauth2_token_exchange 사용 — MCP OBO Auth 참고. |
oauth2_flow |
예 | 흐름 선택기. "client_credentials"(M2M) 또는 "authorization_code"(interactive PKCE, delegate_auth_to_upstream 포함) 중 하나. config.yaml의 모든 auth_type: oauth2 서버에 필수. 프록시는 누락되거나 잘못되면 시작 거부. UI로 만든 서버는 OAuth 흐름 타입 선택기에서 가져옴. 이 필드가 생기기 전에 만든 레거시 데이터베이스 행만 요청 시점에 필드 형태에서 추론으로 폴백하며, config 항목은 절대 추론되지 않음. |
client_id |
M2M은 예, interactive는 선택 | OAuth2 클라이언트 ID. client_credentials에 필수. interactive 흐름은 업스트림이 지원하면 POST /{server_name}/register에서 Dynamic Client Registration(RFC 7591)으로 얻을 수 있음. os.environ/VAR_NAME 지원. |
client_secret |
M2M은 예, interactive는 선택 | OAuth2 클라이언트 시크릿. client_id와 동일 적용. os.environ/VAR_NAME 지원. |
token_url |
M2M은 예, interactive는 선택 | 토큰 엔드포인트 URL. LiteLLM이 client_credentials와 authorization-code 교환에 여기로 POST. |
authorization_url |
Interactive 전용 | 업스트림 인증 엔드포인트. 있으면 LiteLLM이 서버를 interactive PKCE로 취급하고 GET /{server_name}/authorize를 이 URL로 프록시. |
registration_url |
선택 | 업스트림 Dynamic Client Registration 엔드포인트(RFC 7591). 있으면 POST /{server_name}/register가 이 URL을 통해 프록시. |
scopes |
아니오 | 요청할 스코프 목록. M2M은 토큰 요청의 scope 파라미터로, interactive는 authorize 요청에 전달. |
token_validation |
아니오 | /token 교환 후 OAuth 토큰 응답에 대해 검사되는 키-값 규칙 dict. 규칙 불일치는 token_validation_failed로 교환 실패. {"team.enterprise_id": "T12345"} 같은 테넌트 클레임 주장에 유용. |
token_storage_ttl_seconds |
아니오 | (interactive 흐름) 사용자별 토큰 캐시의 TTL 재정의. 미설정 시 LiteLLM은 토큰 응답의 expires_in - buffer 사용. |
OAuth 디버깅 (Debugging OAuth)
LiteLLM 프록시가 원격으로 호스팅되고 서버 로그에 접근할 수 없으면 debug 헤더를 활성화해 HTTP 응답에서 마스킹된 인증 진단을 얻으세요.
디버그 모드 활성화
MCP 클라이언트 요청에 x-litellm-mcp-debug: true 헤더를 추가하세요.
Claude Code:
claude mcp add --transport http litellm_proxy http://proxy.example.com/atlassian_mcp/mcp \
--header "x-litellm-api-key: *** sk-..." \
--header "x-litellm-mcp-debug: true"
curl:
curl -X POST http://localhost:4000/atlassian_mcp/mcp \
-H "Content-Type: application/json" \
-H "x-litellm-api-key: *** sk-..." \
-H "x-litellm-mcp-debug: true" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
디버그 응답 헤더 읽기
응답에 다음 헤더가 있어요(모든 민감 값은 마스킹):
| 헤더 | 설명 |
|---|---|
x-mcp-debug-inbound-auth |
어떤 인바운드 인증 헤더가 있었는지. |
x-mcp-debug-oauth2-token |
OAuth2 토큰(마스킹). LiteLLM 키가 새면 SAME_AS_LITELLM_KEY 표시. |
x-mcp-debug-auth-resolution |
사용된 인증 방법: oauth2-passthrough, m2m-client-credentials, per-request-header, static-token, no-auth. |
x-mcp-debug-outbound-url |
업스트림 MCP 서버 URL. |
x-mcp-debug-server-auth-type |
서버에 구성된 auth_type. |
예시, 건강한 OAuth2 패스스루:
x-mcp-debug-inbound-auth: x-litellm-api-key=Bearer****1234; authorization=Bearer****ef01
x-mcp-debug-oauth2-token: Bearer****ef01
x-mcp-debug-auth-resolution: oauth2-passthrough
x-mcp-debug-outbound-url: https://mcp.atlassian.com/v1/mcp
x-mcp-debug-server-auth-type: oauth2
예시, LiteLLM 키 누출(잘못 구성):
x-mcp-debug-inbound-auth: authorization=Bearer****1234
x-mcp-debug-oauth2-token: Bearer****1234 (SAME_AS_LITELLM_KEY - likely misconfigured)
x-mcp-debug-auth-resolution: oauth2-passthrough
x-mcp-debug-outbound-url: https://mcp.atlassian.com/v1/mcp
x-mcp-debug-server-auth-type: oauth2
일반적인 문제 (Common Issues)
LiteLLM API 키가 MCP 서버로 누출
증상: x-mcp-debug-oauth2-token이 SAME_AS_LITELLM_KEY를 표시.
Authorization 헤더가 OAuth2 토큰 대신 LiteLLM API 키를 담아요. 클라이언트가 이미 Authorization 헤더를 설정했으므로 OAuth2 흐름이 실행되지 않았어요.
수정: LiteLLM 키를 x-litellm-api-key로 옮겨 주세요:
# WRONG — blocks OAuth2 discovery
claude mcp add --transport http my_server http://proxy/server/mcp \
--header "Authorization: Bearer ***"
# CORRECT — LiteLLM key in dedicated header, Authorization free for OAuth2
claude mcp add --transport http my_server http://proxy/server/mcp \
--header "x-litellm-api-key: *** sk-..."
OAuth2 토큰 없음
증상: x-mcp-debug-oauth2-token이 (none)이고 x-mcp-debug-auth-resolution이 no-auth.
다음을 확인하세요:
Authorization헤더가 클라이언트 구성의 정적 헤더로 설정되어 있지 않은지.- LiteLLM config의 MCP 서버에
auth_type: oauth2가 있는지. .well-known/oauth-protected-resource엔드포인트가 유효한 메타데이터를 반환하는지.
사용자 토큰 대신 M2M 토큰 사용
증상: x-mcp-debug-auth-resolution이 m2m-client-credentials를 표시.
서버에 client_id/client_secret/token_url이 구성되어 있어 LiteLLM이 사용자별 OAuth2 토큰 대신 machine-to-machine 토큰을 가져와요. 사용자별 토큰을 쓰려면 서버 구성에서 클라이언트 자격 증명을 제거하세요.
패스스루 및 위임된 업스트림 OAuth (Passthrough and Delegated Upstream OAuth)
클라이언트가 이미 업스트림 자신의 OAuth issuer에 직접 인증하는 서버에는 LiteLLM이 자체적으로 토큰을 관리하는 대신 클라이언트의 업스트림 토큰을 전달할 수 있어요. 투명 auth_type: true_passthrough 모드, 입장 게이팅 auth_type: oauth_delegate 모드, 레거시 delegate_auth_to_upstream 플래그는 MCP OAuth Passthrough에서 다룹니다. 그 페이지는 또한 OpenCode, Claude Code, Cursor, Claude Desktop 같은 OAuth 전용 클라이언트용 dcr_bridge 플래그를 문서화하며, 여기서 게이트웨이가 등록·로그인을 호스팅해 클라이언트가 단일 OAuth 흐름으로 연결할 수 있어요.
더 알아보기 (Learn more)
- MCP OAuth Passthrough — 투명·위임 OAuth
- MCP OBO Auth — On-Behalf-Of 토큰 교환
- MCP 문제 해결 — 디버그 헤더·OAuth 문제