MCP 서버가 사용자를 대신하여 너의 API 를 호출하는 방법: 프로덕션 토큰 전략
MCP 서버의 아웃바운드 토큰 전략을 설명합니다: 토큰 패스스루와 M2M 이 왜 실패하는지, 그리고 토큰 교환이 어떻게 사용자의 권한과 정렬을 유지하는지에 대해 다룹니다.
MCP 서버의 아웃바운드 토큰 전략을 설명합니다: 토큰 패스스루와 M2M 이 왜 실패하는지, 그리고 토큰 교환이 어떻게 사용자의 권한과 정렬을 유지하는지에 대해 다룹니다.
이전 글에서 우리는 Logto 원격 MCP 서버를 구축한 전체적인 경험을 공유했습니다. 이 글에서는 아키텍처 설계와 OAuth 플로우를 더 자세히 다룹니다.
MCP 서버를 경계로 할 때, 원격 MCP 서버의 인증은 인바운드와 아웃바운드 두 부분으로 나눌 수 있습니다.
인바운드 부분은 MCP 명세에서 이미 잘 정의되어 있으며, 커뮤니티에서도 많은 논의와 구현이 있습니다. 구현 가이드도 작성한 적이 있습니다. 반면 아웃바운드는 상대적으로 덜 주목받고 있습니다: MCP 서버는 자기 자신에 접근할 때 사용하는 토큰만 가지고 있는데, 어떻게 사용자를 대신해 비즈니스 API 를 호출할 수 있을까요?
이 질문은 Logto MCP 서버를 만드는 동안 계속 고민했던 부분입니다. Logto Cloud 는 전형적인 B2B 멀티테넌트 제품이고, 사용자는 여러 테넌트에 속할 수 있으며, 권한은 각 테넌트에서의 사용자의 역할에서 결정됩니다. AI 도 단순히 사용자를 대행하는 것뿐 아니라 올바른 테넌트에서 동작해야 합니다.
이 글은 실제 우리의 결정 과정을 따라가며 설명합니다:
OAuth 관점에서 원격 MCP 서버는 동시에 두 가지 역할을 수행합니다:
즉, MCP 서버는 양쪽에서 역할이 다르고, 각각 서로 다른 토큰을 사용합니다. MCP 클라이언트가 OAuth 를 통해 얻는 토큰의 audience 는 MCP 서버이기 때문에, 비즈니스 API 에서는 audience 검증 시 이를 거절합니다.
즉 아웃바운드는 본질적으로 위임(delegation) 문제: MCP 서버가 사용자의 자격 증명을 직접 들고 있지 않으면서 어떻게 사용자를 대신해, 동시에 그 사용자의 권한 범위 내에서 downstream API 를 호출할 수 있을까요?
MCP 엔드포인트를 비즈니스 API 서버 내부에 구현해서 프로세스와 인증 스택을 공유하는 것이 가장 직접적인 방식처럼 보일 수 있습니다. 하지만 우리는 독립적인 배포를 선택했습니다:
MCP 서버는 mcp.logto.io 고유 도메인에 배포되어 있고, 본 서비스와 사적으로 엮이지 않습니다. 오픈 소싱을 하더라도 별 무리가 없습니다.
내장형 옵션도 토큰 문제를 피할 수는 없습니다: MCP 엔드포인트와 비즈니스 API 가 동일한 리소스 식별자를 공유하므로, MCP 클라이언트가 획득하는 토큰은 모든 API 권한이 포함됩니다. "이 호출은 누구의 권한으로 동작하는가"라는 문제는 서비스 간 토큰 교환 대신 내부 권한 전달 문제로 옮겨가기만 할 뿐, 근본 문제는 남습니다. 독립 배포는 권한 경계를 명시적으로 설계하도록 강제합니다. 이하에서 다루는 핵심입니다.
아웃바운드 논쟁은 더 기본적인 질문에서 시작합니다: MCP 클라이언트가 받는 토큰의 audience 는 무엇이어야 할까요?
비즈니스 API 의 리소스 식별자를 재사용하는 것이 가장 직접적인 방식입니다: Logto 의 Management API 는 이미 표준 OAuth 보호 리소스입니다. MCP 클라이언트는 바로 이 리소스용 토큰을 요청해 MCP 서버가 그대로 포워딩하면 되니, 초기 MCP 서버 구현체는 다 이렇게 했습니다.
단점: MCP 토큰의 audience 가 비즈니스 API 인 경우, 권한 경계가 사라집니다.
그래서 우리는 다음과 같이 결정: MCP 서버는 별도의 보호 리소스이며, 고유한 리소스 식별자(https://mcp.logto.io)와 스코프를 가집니다.
이로써 권한 경계가 명확해지며, 아웃바운드 문제도 구체적으로 변합니다: 이 토큰은 오직 MCP 서버에만 접근할 수 있는데, MCP 서버는 뭘 들고 API 를 호출해야 할까요?
아래에서 후보 방식들을 살펴봅니다.
가장 자연스럽게 떠오른 첫 아이디어입니다.
Logto Console 은 SPA 입니다. Management API 호출 방법은 단순: 브라우저에서 OAuth 로그인 → API audience 의 토큰 획득 → 프론트가 직접 호출.
그렇다면 MCP 서버가 또다른 Console 처럼 동작할 수 있을까요? MCP 클라이언트가 비즈니스 API 토큰을 요청해서 MCP 서버가 변환 없이 그대로 전달하도록 하는 겁니다:
이 방식의 장점은 극단적으로 단순하다는 겁니다. MCP 서버는 그냥 토큰을 전달만 합니다. 그러나 명백한 문제점이 있습니다:
첫째, 위에서 내린 권한 경계 결정과 정면으로 충돌합니다. 토큰 패스스루 방식은 MCP 클라이언트가 비즈니스 API 토큰을 갖길 요구하는데, 바로 우리가 거부한 방식입니다.
둘째, MCP 서버가 더이상 보호 리소스가 아닙니다. MCP 서버가 받는 토큰의 audience 가 자기 자신이 아니므로 audience 검증이 무의미해지고, 서명 및 issuer 검증만 남게 되며, MCP 명세에서 정의한 보호 리소스(security) 역할에 맞지 않습니다(RFC 9728 참고). 사실상 백엔드를 SPA 처럼 위장하는 셈.
셋째, MCP 클라이언트가 따라오지 않습니다. 명세를 준수하는 MCP 클라이언트는 Protected Resource Metadata 에 따라 MCP 서버 audience 로 토큰을 요청합니다. 비즈니스 API 용 토큰을 요청할 방법이 표준에 없습니다. 즉, 현실적으로 채택 불가.
사용자 토큰이 안 된다면, MCP 서버 자신의 자격 증명은 어떨까요?
MCP 서버에 M2M(머신 대 머신) 애플리케이션 자격증명을 주고, 클라이언트 크레덴셜로 비즈니스 API 용 토큰을 받아 호출하게 할 수도 있습니다. 내부 서비스간 통신의 표준 방식이기도 하죠.
이제 인바운드 인증도 잘 동작합니다: MCP 클라이언트는 MCP 서버 audience 로 토큰을 제출, MCP 서버가 검증 후, 자기 자신의 M2M 토큰으로 일 처리를 합니다.
致命적인 결함은 M2M 토큰의 권한이 사용자 권한과 무관하다는 점입니다:
M2M 은 사용자 컨텍스트가 없는 경우(예: 예약 작업, 시스템간 동기화)에 적합합니다. MCP 서버는 달라야 합니다: 모든 호출은 특정 사용자로부터 시작되므로, 해당 사용자의 신원과 권한에 맞게 동작해야 합니다.
두 방식의 교훈을 합치면 올바른 설계의 요구 사항이 도출됩니다:
이 요구 조건을 만족하는 표준 메커니즘이 있습니다: 토큰 교환(RFC 8693). MCP 서버는 사용자를 대표하는 자격 증명을 들고 인증 서버에서 다운스트림 API 토큰으로 교환합니다. 사용자의 신원과 권한은 교환 과정에서 그대로 유지됩니다.
Logto 에서는 이 "사용자를 대표하는 자격 증명"이 사용자 가장(user impersonation) 기능의 subject token 입니다. 서버가 특정 사용자에 대해 요청해서 받는 단기간/1회용 자격 증명으로, "다음 토큰 교환은 이 사용자로 동작함"을 의미합니다. 사용자가 직접 생성/설정할 필요 없이 자동화되어 있습니다. subject token 은 짧은 만료시간을 가지며 1회만 사용 가능합니다.
아키텍처에는 총 4개 역할이 있습니다. MCP 서버는 mcp.logto.io 에 독립 배포됩니다:
툴 호출 한 번에 숨어있는 전체 토큰 플로우는 아래와 같습니다:
단계별로 상세히 보면:
① 인바운드 검증. MCP 클라이언트가 사용자 토큰과 함께 툴을 호출합니다. 토큰의 audience 는 MCP 서버의 리소스 식별자이고, scope 는 mcp:all 입니다. MCP 서버는 서명, issuer, audience, scope 모두 검증 후 사용자의 신원을 추출합니다. 인바운드는 여기서 끝이며, 이 토큰은 절대 downstream 호출에 쓰이지 않습니다.
② 서비스 신원 확인. MCP 서버는 자체 M2M 자격증명으로 access:mcp:api 스코프의 액세스 토큰을 받아옵니다. 이 스코프는 다음 단계의 엔드포인트 호출만을 위해 별도 부여됨.
③ subject token 요청. 전체 체인에서 가장 중요한 단계입니다. MCP 서버는 POST /api/mcp/subject-tokens (Cloud 에서 MCP 용 별도 오픈) 엔드포인트에 2개의 자격증명을 동시에 제출합니다:
Authorization 헤더: M2M 토큰(MCP 공식 서버임을 증명)x-mcp-user-token 헤더: 사용자 토큰(본 사용자가 나를, 그리고 권한이 아직 유효함을 증명)Cloud 에서는 사용자 토큰을 완전히 검증: 서명, issuer, 만료, audience 가 MCP 서버의 리소스 식별자, scope 는 mcp:all 포함이어야 함. 검증 완료 후 userId 는 토큰 sub 클레임에서 직접 추출. 이 엔드포인트는 사용자 아이디를 파라미터로 받지 않음.
이 설계는 M2M 자격증명 남용을 방지합니다. 엔드포인트가 사용자 아이디를 받으면, M2M 자격증명만 있으면 누구든 모든 사용자를 가장할 수 있기 때문. 위와 같이 설계하면, 본인의 valid 권한이 있는 경우에만 MCP 서버가 교환을 진행할 수 있습니다.
발급된 subject token 은 짧은 만료, 1회용. 서버는 절대 캐싱하지 않고 매번 새로 요청해서 씁니다.
④ 워크 토큰으로 교환. subject token 으로 표준 토큰 교환을 진행하여 필요에 따라 두 종류의 토큰을 얻습니다:
⑤ 아웃바운드 호출. 교환된 토큰으로 비즈니스 API 를 호출한 후, MCP 클라이언트에게 결과 전달.
멀티테넌트 맥락 역시 교환 단계에서 결정됩니다: 테넌트 정보는 연결 단계가 아닌 토큰 교환 계층에서 처리됩니다. list_tenants 명령은 Cloud API 토큰으로 옵션을 받고, 사용자가 대화에서 선택하면, MCP 서버가 해당 테넌트용 org 토큰을 교환합니다. 한 엔드포인트로 모든 테넌트 처리, per-tenant 별도 배포 불필요, 대화 중 테넌트를 신규로 만들어도 즉시 반영.
이전 방식의 문제점들과 비교하면 전부 보완됩니다:
x-mcp-user-token 체크 실패 → 아웃바운드 체인 즉시 중단결과적으로, M2M 자격증명은 최종 설계에서 딱 올바른 용도를 가집니다: 서비스 신원 증명용이고, "사용자 행세하기" 권한은 반드시 그 사용자의 실질적인 유효 권한이 있을 때만 현장에서 각각 교환해야 합니다.
이 전 과정을 요약하면, 원격 MCP 서버의 토큰 전략은 다음 포인트로 귀결됩니다:
간단 테스트: MCP 클라이언트가 갖고 있는 토큰이 유출됐다고 가정할 때, 공격자가 할 수 있는 일은 오직 MCP 서버의 제한된 툴들을 사용자의 권한 내에서 호출하는 것뿐이어야 합니다. 만약 그 토큰으로 곧장 전체 API 호출이 가능하다면 권한 경계가 이미 허물어진 것입니다.
MCP 생태계는 지금도 빠르게 변화 중입니다. 인바운드 인증은 명세가 완전히 잘 정리되어 있지만, "MCP 서버가 downstream API 를 어떻게 호출하느냐"는 각 팀의 해법에 달려 있습니다. 우리의 사례가 참고가 되길 바랍니다.
너의 제품에 MCP 서버를 구축하려면 Logto 의 AI 시나리오용 솔루션: AI 앱, 에이전트, MCP 서버 인증도 참고하세요. 이 체인이 실제로 작동하는 모습을 보고 싶다면 Logto MCP 서버 에 바로 접속해 시험해볼 수 있습니다.
본문에서 언급한 기술 참고 자료: