Zettelkasten

Higgsfield MCP OAuth로 받은 토큰은 조회만 되고 생성 작업 제출은 거부된다

·수정 2회

요약

  • 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 인가 서버가 그 스코프를 광고하지 않으면 클라이언트가 손쓸 방법이 없다.
  • 판별법: 같은 계정에서 작동하는 다른 클라이언트를 하나 확보해 동일 요청을 태워본다. 되면 요청 내용은 결백하고 자격증명만 남는다. 이 대조군 하나가 파라미터 튜닝 수십 번보다 빠르다.
  • 조회는 되는데 쓰기 일부만 실패하면 "서버 장애"로 결론짓기 전에 스코프를 본다. 인증이 통과했다는 사실이 인가까지 충분하다는 뜻은 아니다.

관련 노트

참고