요약
- MCP 서버가 자기 OAuth 창구로 발급한 토큰이 그 서버의 일부 툴에서만 통할 수 있다. Higgsfield에서 조회·업로드·비용견적은 되는데 생성 작업 제출만 거부됐다.
- 벤더의 공식 CLI(
higgsfield auth login)는 다른 인가 서버·다른 스코프를 쓰고, 그 토큰은 같은 MCP 엔드포인트에서 생성까지 된다. - "같은 서버·같은 계정인데 툴 일부만 실패"하면 파라미터가 아니라 토큰을 어느 창구에서 받았는지를 먼저 의심한다.
- 유력한 메커니즘: 과금 주체(조직)를 토큰에 싣는 스코프가 MCP 경로엔 없어서, 돈이 나가는 호출만 거부된다.
본문
증상 (직접 재현됨)
자체 에이전트 웹앱을 MCP 규격대로 붙였다. OAuth 2.0 PKCE + 동적 클라이언트 등록(DCR)으로 승인받고 Bearer 토큰으로 호출하는, 표준 그대로의 구성이다.
같은 토큰 하나로 이렇게 갈렸다.
| 호출 | 결과 |
|---|---|
| 잔액·생성 이력·레퍼런스 목록 조회 | 정상 |
media_upload (업로드 presigned URL 발급, 쓰기) |
정상 |
select_workspace (쓰기) |
정상 |
generate_image에 get_cost: true (비용 견적) |
정상 |
generate_image 실제 제출 |
매번 즉시 실패 |
에러는 Something went wrong. Please try again. + request ID뿐이고 사유가 없다. 실패한 제출은 작업 기록으로도 남지 않아 job 조회로 파고들 대상조차 없었다. 즉 작업이 만들어지기 전에 거부된다.
배제한 것들 (전부 직접 테스트)
- 파라미터: 모델 3종, 레퍼런스 유무, 비율, 컷 수, 과금 플래그 유무 → 전부 동일 실패
- 계정·워크스페이스·크레딧: 동일 계정에서 다른 클라이언트는 성공하므로 계정 문제 아님
- 클라이언트 등록 상함: 새
client_id로 재등록·재승인 → 동일 실패 - MCP
clientInfo이름, 클라이언트 capability 선언 → 무관 - 토큰 만료: 조회가 되는 것 자체가 유효하다는 증거
원인: 인가 창구가 둘이고 스코프가 다르다
| MCP OAuth (앱) | 공식 CLI | |
|---|---|---|
| 승인 주소 | mcp.higgsfield.ai/oauth2/authorize |
clerk.higgsfield.ai/oauth/authorize |
| client_id | 매 앱마다 동적 등록 | 고정(퍼스트파티) |
| scope | openid email offline_access |
email profile offline_access user:org:read |
| 작업 제출 | 거부 | 정상 |
MCP 서버의 .well-known/oauth-authorization-server가 광고하는 scopes_supported는 openid·email·offline_access 셋뿐이다. 그 경로로는 user:org:read가 붙은 토큰을 받을 방법이 아예 없다. 규격대로 붙였는데도 권한이 모자란 토큰만 받게 되는 구조다.
왜 하필 작업 제출만 막히나 (문서 근거 + 관찰, 벤더 확인은 아님)
이 서비스의 인증은 Clerk를 쓴다. Clerk 문서에 따르면 user:org:read 스코프를 요청해야 동의 화면에 Organization 선택기가 뜨고, 사용자가 고른 뒤에야 org_id 클레임이 액세스 토큰에 들어간다. 요청하지 않으면 토큰에 조직 정보가 없다.
맞아떨어지는 정황:
- 인증 서버(Clerk)의
claims_supported에org_id가,scopes_supported에user:org:read가 있다. 반면 MCP 인가 서버 메타데이터는 그 스코프를 광고하지 않는다. - MCP OAuth로 받은 토큰의 ID 토큰 클레임을 디코드해보면
org_id가 없다 (관찰됨). - 공식 CLI는 워크스페이스를 고르기 전엔 생성을 거부하고, 도움말은 그 명령을 "Select billing workspace"라고 부른다.
즉 워크스페이스 = Clerk Organization이고, 작업 제출은 과금 주체가 토큰에 박혀 있어야 하는데 MCP OAuth 토큰엔 그게 없다. 조회·업로드·비용견적은 과금 주체가 필요 없으니 통과한다. 증상의 경계가 정확히 이 선에서 갈린다.
다만 서버가 실제로 그 클레임을 검사하는지는 확인하지 못했다. 두 토큰을 Clerk userinfo 엔드포인트에 태워 org_id 유무를 직접 대조하려 했으나 둘 다 403이었다.
해결
공식 CLI가 토큰을 그대로 뱉어주면 그걸 쓰면 된다.
npm i -g @higgsfield/cli
higgsfield auth login # 브라우저 PKCE 로그인
higgsfield workspace set <workspace_id>
higgsfield auth token # 액세스 토큰 출력 → 앱 환경변수로
앱 코드는 한 줄도 안 고치고, 환경변수의 Bearer 토큰만 CLI 것으로 갈아끼우면 생성까지 동작한다. 단 앱이 저장된 OAuth 토큰을 환경변수보다 우선하는 구조라면 저장분을 먼저 비워야 새 토큰이 실제로 쓰인다 — 이 순서를 놓치면 "바꿨는데 그대로"가 된다.
일반화할 교훈
- 벤더가 MCP와 CLI를 따로 내놓으면 인가 서버가 둘일 수 있다. 어느 쪽 토큰이냐에 따라 권한이 갈린다.
- 스코프 부족은 401/403이 아니라 무의미한 500성 에러로 나올 수 있다. 에러 메시지로 스코프 문제를 알아채는 건 기대하지 말 것.
- 과금 주체(조직·워크스페이스)를 토큰에 싣는 스코프를 놓치면, 읽기는 다 되는데 돈 나가는 호출만 죽는다. 인증 문제가 아니라 청구 대상이 없는 문제다. MCP 인가 서버가 그 스코프를 광고하지 않으면 클라이언트가 손쓸 방법이 없다.
- 판별법: 같은 계정에서 작동하는 다른 클라이언트를 하나 확보해 동일 요청을 태워본다. 되면 요청 내용은 결백하고 자격증명만 남는다. 이 대조군 하나가 파라미터 튜닝 수십 번보다 빠르다.
- 조회는 되는데 쓰기 일부만 실패하면 "서버 장애"로 결론짓기 전에 스코프를 본다. 인증이 통과했다는 사실이 인가까지 충분하다는 뜻은 아니다.
관련 노트
- Claude 구독을 서드파티 도구에 붙이려면 토큰 추출이 아니라 claude CLI subprocess 프록시를 거친다
- Play Console SA는 앱 권한만으로는 purchases.subscriptions.get이 401을 반환한다
- Google Play 개발자 API 권한 모델은 GCP IAM과 Play Console 두 레이어로 분리된다
참고
- https://clerk.com/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth —
user:org:read스코프와org_id클레임 - https://higgsfield.ai/mcp
- https://higgsfield.ai/cli
- https://www.npmjs.com/package/@higgsfield/cli
- https://mcp.higgsfield.ai/.well-known/oauth-protected-resource
- https://mcp.higgsfield.ai/.well-known/oauth-authorization-server
- https://clerk.higgsfield.ai/.well-known/openid-configuration