에이전트가 기능을 호출하는 방법보다 먼저, 누구의 권한으로 어느 데이터에 접근하는지 정리했다. 서버형 MCP와 WebMCP를 검토하면서 나눈 인증 경계와 도입 기준을 기록한다.
시작하며
웹에서 사용하던 조회 기능을 AI 에이전트에도 제공하려고 했다. 사용자가 화면에서 대상을 고르고 버튼을 누르는 대신, 에이전트가 필요한 데이터를 조회하도록 만드는 것이다.
처음에는 기존 API 앞에 MCP 서버를 두고 도구를 등록하면 될 것처럼 보였다. 하지만 호출 경로를 그려 보니 도구 정의보다 먼저 결정해야 할 것이 있었다.
에이전트가 누구를 대신하며, 그 사용자가 접근할 수 있는 데이터 중 어디까지 허용할 것인가.
기존 서비스에서는 로그인한 사용자라도 모든 작업 공간을 조회할 수 없었다. 사용자와 작업 공간 사이에 접근 권한이 있었고, 같은 사용자도 공간에 따라 가능한 작업이 달랐다.
이 구조를 유지하면서 두 가지 경로를 검토했다. 하나는 별도의 MCP 서버에서 백엔드 API를 호출하는 방식이고, 다른 하나는 웹페이지가 WebMCP 도구를 제공하는 방식이다. 아직 운영에 적용한 결과가 아니라, 구현 전에 인증과 운영 범위를 나누어 본 설계 이야기다.
처음 비교할 때 놓친 두 가지
처음 정리한 서버형 안에서는 API Key로 사용자 ID를 찾은 뒤 기존 사용자 요청처럼 처리하면 된다고 봤다. 기존 인가 로직을 재사용할 수 있다는 점에 집중한 것이다. 그런데 이 흐름에는 에이전트 연결을 읽기 전용으로 제한할 위치가 빠져 있었다. 사용자가 편집 권한을 가지고 있다면 사용자 ID만 복원한 요청은 수정까지 허용될 수 있었다.
WebMCP 쪽도 처음에는 기존 로그인 세션을 사용하므로 프론트엔드에 도구 등록 코드만 추가하면 된다고 정리했다. 하지만 로그인 상태를 재사용하는 것과 새 호출 경로를 검증하는 것은 다른 일이었다. 화면에서 선택한 대상이 바뀌거나 세션이 만료되는 상황까지 다루려면 도구 등록 밖의 처리도 필요했다.
그래서 비교표에 신규 컴포넌트 수뿐 아니라 요청마다 검사할 권한, 실행 대상이 결정되는 시점, 연결을 끊은 뒤의 처리를 추가했다. 이 세 항목을 넣으니 어느 쪽이 더 간단한지보다 어느 쪽의 운영 책임을 감당할 것인지가 보였다.
로그인 성공과 데이터 접근 허용은 다르다
내가 유지하려던 기준은 다음과 같다.
요청 주체 확인
→ 대상 작업 공간 확인
→ 해당 공간에 대한 사용자 권한 확인
→ 요청한 작업 허용 여부 확인
에이전트라는 이유로 마지막 두 단계를 건너뛰면 안 된다. 유효한 토큰이 있다는 사실은 요청 주체를 확인하는 근거이지, 모든 데이터에 접근해도 된다는 뜻은 아니다.
예를 들어 사용자가 작업 공간 A에는 읽기 권한만 있고, B에는 편집 권한이 있다고 하자. 에이전트용 자격 증명을 발급하더라도 A를 수정할 수 있어서는 안 된다. 반대로 에이전트 연결 자체를 읽기 전용으로 제한했다면, 사용자가 B의 편집자여도 그 연결을 통한 수정은 막아야 한다.
실제로 허용할 작업
= 사용자의 현재 대상별 권한 ∩ 에이전트 연결에 허용한 권한
이 교집합을 서버에서 검사해야 한다. 도구 설명에 “읽기 전용”이라고 적거나 수정 도구를 화면에서 숨기는 것만으로는 권한 제한이 되지 않는다.
서버형 MCP: 브라우저 밖에서 요청하려면 별도의 인증 경계가 생긴다
서버형 경로에서는 에이전트의 MCP 클라이언트가 서버에 연결하고, 서버가 필요한 백엔드 API를 호출한다.
에이전트의 MCP 클라이언트
→ MCP 서버: 클라이언트 인증과 허용된 도구 확인
→ API Gateway: 백엔드용 자격 증명 검증
→ 내부 서비스: 대상 데이터에 대한 접근 권한 검증
브라우저 페이지가 열려 있지 않아도 사용할 수 있다는 점이 이 경로의 장점이다. 다만 MCP 서버에 접속할 권한과 백엔드 API를 호출할 권한을 같은 것으로 취급하면 안 된다.
처음 검토한 안은 사용자별 API Key를 발급하고 기존 Gateway에서 사용자로 연결하는 방식이었다. 이때 API Key는 백엔드 접근용 자격 증명으로 볼 수 있다. 그러나 이것만 정했다고 모든 MCP 클라이언트의 인증 흐름까지 해결되는 것은 아니다.
HTTP 기반 MCP에서 인가를 제공할 때 따르는 표준 흐름은 OAuth를 바탕으로 한다. MCP 자체가 모든 연결에 OAuth를 요구하는 것은 아니며, 로컬 stdio와 원격 HTTP의 인증 구성을 구분해야 한다. 지원하려는 클라이언트가 어떤 연결 방식을 사용하는지도 별도로 확인해야 한다. MCP Authorization
특히 클라이언트가 보낸 토큰을 MCP 서버에서 검증하지 않고 그대로 하위 API로 전달하는 구성을 피해야 한다. MCP 보안 지침에서도 이러한 token passthrough를 금지한다. MCP Security Best Practices
따라서 원격 서버를 선택한다면, MCP 서버용 토큰을 검증한 뒤 승인된 사용자 연결에 해당하는 백엔드 자격 증명으로 API를 호출하는 구조가 필요하다. 두 자격 증명의 발급 대상, 허용 범위, 만료와 폐기를 구분해야 한다.
내가 검토한 사용자별 백엔드 API Key 안을 기준으로 나누면 다음과 같다.
| 구간 | 확인할 자격 증명 | 검증 책임 |
|---|---|---|
| MCP 클라이언트 → 원격 MCP 서버 | 해당 MCP 서버용 액세스 토큰 | MCP 서버가 대상과 유효성을 검증한다 |
| MCP 서버 → API Gateway | 승인된 사용자 연결에 보관한 백엔드 API Key | Gateway가 키의 소유자, 만료, 폐기와 허용 범위를 확인한다 |
| 내부 서비스 → 대상 데이터 | 검증된 사용자와 연결의 허용 범위 | 대상별 현재 권한과 요청 작업을 함께 검사한다 |
두 자격 증명을 사용하는 방식이 모든 MCP 서버의 필수 구조라는 뜻은 아니다. 여기서는 기존 백엔드에 사용자별 API Key를 추가하는 안을 검토했기 때문에 이처럼 분리했다. 핵심은 MCP 서버가 받은 토큰을 백엔드도 그대로 받아 줄 것이라고 가정하지 않는 것이다.
API Key를 추가한다면 키 문자열보다 수명주기가 먼저다
백엔드용 API Key를 도입하는 안에서는 다음 항목까지 함께 고려했다.
| 항목 | 설계 기준 |
|---|---|
| 소유자 | 발급 요청의 인증 정보에서 결정하고, 요청 본문의 사용자 ID를 그대로 믿지 않는다 |
| 원본 키 | 충분히 긴 난수로 만들고 발급 시 한 번만 보여준다 |
| 검증용 저장 | 발급 서비스에는 원본 대신 해시와 식별용 접두부를 보관한다 |
| 기본 권한 | 필요한 조회 기능부터 허용하고, 쓰기 권한은 별도로 승인한다 |
| 만료와 폐기 | 사용자가 연결을 끊으면 이후 요청을 차단하고, 검증 캐시의 무효화 정책도 정한다 |
| 사용자 권한 변경 | 키 발급 당시 권한을 영구 복제하지 않고 현재 대상별 권한을 검사한다 |
발급 서비스의 해시 저장과 호출 측의 비밀 보관은 다른 문제다. 백엔드 API에 키를 보내는 MCP 서버에는 사용할 수 있는 원본이 필요하므로, 별도의 비밀 저장소나 보호된 설정에서 관리해야 한다. 도구 인자나 모델에게 전달하는 대화에 키를 넣지 않는다.
API Key로 사용자 ID를 찾은 뒤 기존 사용자 요청처럼 처리하는 것만으로는 읽기 전용 제한이 생기지 않는다. 연결의 허용 범위가 요청 처리 과정에서 사라지지 않도록 Gateway나 서비스의 인가 단계까지 전달하고 검사해야 한다.
예를 들어 검증 결과에 사용자 ID만 남기면 서비스는 그 요청이 읽기 전용 연결에서 왔다는 사실을 알 수 없다. 사용자 식별자와 함께 연결에 허용된 작업 범위를 전달하고, 기존 대상별 권한 검사와 함께 사용해야 한다. 이 값은 외부에서 보낸 헤더를 그대로 신뢰해 만들면 안 되고, 서버에서 검증한 자격 증명을 기준으로 구성해야 한다.
WebMCP: 로그인한 페이지의 기능을 도구로 제공한다
WebMCP는 웹페이지가 브라우저의 에이전트에 구조화된 도구를 제공하는 접근이다. 화면을 보고 버튼 위치를 추측하게 하는 대신, 사이트가 실행 가능한 기능과 입력 형식을 명시한다. 원격 MCP 서버를 브라우저에 그대로 옮겨 놓는 것과는 다르다. Chrome WebMCP 문서
내가 검토한 경로에서는 도구가 기존 웹 API 클라이언트를 이용한다.
브라우저의 에이전트
→ 로그인한 페이지의 도구 실행
→ 기존 웹 API 클라이언트
→ API Gateway와 내부 서비스의 기존 인증 및 인가
이렇게 하면 사용자에게 별도 API Key를 발급하고 등록하게 하는 과정을 줄일 수 있다. 로그인 정보는 기존 웹 요청 경로에서 사용하고, 토큰 자체를 도구 반환값으로 내보낼 필요도 없다.
다만 “프론트엔드에 도구만 등록하면 끝”이라고 보기는 어렵다. 로그인한 페이지의 기능을 새로운 호출 주체에게 열어 주므로, 그 경로도 검증 대상이 된다.
화면에서 선택한 대상도 실행 시점에 다시 확인해야 한다
사용자가 A 작업 공간을 보며 조회를 요청한 뒤 B로 화면을 전환할 수 있다. 도구가 실행될 때 전역 상태의 현재 선택값을 읽으면 처음 의도와 다른 데이터를 조회할 수 있다.
나는 대상 식별자를 요청에 명시하고, 결과에도 어느 대상을 조회했는지 포함하는 방향으로 정리했다. 수정 작업이라면 실행 전에 대상과 변경 내용을 보여 주는 확인 과정이 더 중요하다.
가령 사용자가 A를 보고 조회를 요청한 직후 B로 화면을 이동했다고 하자. 도구 실행 직전에 현재 선택값을 읽으면 B를 조회할 수 있다. 입력에 A의 식별자가 들어 있었다면 화면이 바뀌어도 요청 대상은 A로 유지할 수 있다. 이후 응답에 같은 식별자를 포함하면 에이전트도 무엇을 조회했는지 명확히 설명할 수 있다.
또한 도구의 입력 형식을 검사하는 것과 실제 접근 권한을 검사하는 것은 별개다. 형식상 올바른 작업 공간 ID여도 사용자에게 권한이 없으면 백엔드에서 거부해야 한다.
세션을 재사용해도 보안 검증은 줄어들지 않는다
- 로그인 만료 시 재로그인이 필요하다는 결과를 반환하고, 익명 요청으로 조용히 재시도하지 않는다.
- 쿠키 인증을 쓰는 경로라면 기존 CSRF 방어를 유지한다.
- 조회 결과의 문장을 새로운 실행 명령으로 취급하지 않는다. 외부 데이터에 섞인 지시문이 후속 작업의 권한을 바꾸면 안 된다.
- 수정과 삭제는 대상과 영향 범위를 확인한 뒤 실행하도록 설계한다.
- 도구 결과에는 필요한 데이터만 반환하고, 인증 정보와 불필요한 개인정보는 제외한다.
WebMCP를 선택할 때는 지원 브라우저와 에이전트 조합에서 실제 도구 탐색 및 실행이 가능한지도 확인해야 한다. 문서에 API가 있다는 사실만으로 모든 사용자의 브라우저에서 같은 흐름이 된다고 가정할 수는 없다.
비교 기준을 기능 개수가 아니라 실행 환경으로 바꿨다
두 방식을 비교하면서 가장 중요한 질문은 별도 서버를 만들 수 있느냐가 아니었다. 사용자가 페이지를 열고 함께 작업하는 기능인지, 페이지 밖에서도 계속 실행해야 하는 기능인지가 먼저였다.
| 요구사항 | 서버형 MCP를 검토할 이유 | WebMCP를 검토할 이유 |
|---|---|---|
| 사용자가 웹 화면에서 조회를 돕게 한다 | 가능하지만 별도 연결 관리가 필요하다 | 기존 로그인과 화면 흐름을 활용하기 좋다 |
| 브라우저를 닫은 뒤에도 작업한다 | 페이지와 분리된 실행 경로를 만들 수 있다 | 페이지 실행 환경에 의존하는 경로로는 맞지 않는다 |
| 여러 MCP 클라이언트에 제공한다 | 클라이언트별 인증 호환성을 확인하며 서버를 제공한다 | 브라우저와 에이전트의 WebMCP 지원을 먼저 확인한다 |
| 기존 대상별 권한을 유지한다 | 백엔드 자격 증명을 사용자와 연결하고 권한을 재검사한다 | 기존 웹 요청의 인가를 유지한다 |
| 에이전트만 읽기 전용으로 제한한다 | 연결의 범위를 서버에서 강제한다 | 도구 제한뿐 아니라 백엔드에서 강제할 방법도 설계한다 |
| 운영 책임을 정한다 | 서버 배포, 자격 증명 보관, 폐기와 감사를 맡는다 | 페이지 변경, 세션 상태, 도구 수명과 실행 확인을 맡는다 |
현재 단계에서는 한쪽으로 확정하기보다, 우선 제공할 기능의 실행 환경을 정하고 작은 조회 도구로 확인하는 것이 맞다고 봤다. 페이지 안의 보조 기능이 목적이면 WebMCP의 지원 환경부터 확인하고, 브라우저 밖의 자동화가 필요하면 서버형 MCP의 인증 흐름부터 설계하는 식이다.
첫 조회 도구에서 확인할 입력과 결과
두 경로를 비교할 첫 기능은 대상 하나의 요약을 읽는 조회로 좁히는 편이 적절하다고 봤다. 대상을 찾는 과정, 권한 확인, 결과 반환을 한 번에 따라갈 수 있기 때문이다.
조회 입력은 다음처럼 대상과 기간을 명시하는 형태로 잡았다.
{
"workspaceId": "space-a",
"from": "2026-09-01T00:00:00Z",
"to": "2026-09-02T00:00:00Z"
}
사용자 ID와 자격 증명은 도구 인자에 넣지 않는다. 호출자가 임의로 적은 사용자 ID를 믿는 대신 인증된 연결이나 기존 세션에서 요청 주체를 구한다. 기간의 형식과 최대 조회 범위도 서버에서 검사해야 한다.
결과에는 조회 대상, 적용한 기간과 요약 데이터를 함께 담는 방향으로 정리했다.
{
"workspaceId": "space-a",
"period": {
"from": "2026-09-01T00:00:00Z",
"to": "2026-09-02T00:00:00Z"
},
"summary": {
"completedJobs": 12
}
}
권한이 없거나 로그인 세션이 만료된 경우를 빈 조회 결과로 바꾸면 안 된다. 에이전트가 이를 “해당 기간에 데이터가 없다”로 설명할 수 있기 때문이다. 인증 실패, 접근 거부와 정상적인 빈 결과를 구분해 반환하는 것도 첫 도구의 범위에 포함했다.
| 결과 | 도구 호출 측에 전달할 의미 | 후속 처리 |
|---|---|---|
| 정상 조회, 데이터 있음 | 지정한 대상과 기간의 조회 결과 | 결과의 대상과 기간을 기준으로 설명한다 |
| 정상 조회, 데이터 없음 | 조회는 성공했지만 해당 범위에 데이터가 없음 | 다른 기간 조회 여부를 사용자가 판단할 수 있게 한다 |
| 인증 실패 | 연결이나 세션을 사용할 수 없음 | 재연결 또는 로그인을 안내한다 |
| 접근 거부 | 요청 대상이나 작업을 허용하지 않음 | 다른 대상이나 더 높은 권한으로 자동 우회하지 않는다 |
이 입력과 결과의 기준을 먼저 정하면 MCP 서버를 붙이든 페이지에서 실행하든 같은 업무 의미를 유지할 수 있다. 인증 방법이 달라져도 대상과 기간이 조용히 바뀌거나 실패가 빈 데이터로 보이는 상황은 막아야 한다.
도입 전에 확인할 실패 경로
정상 조회 하나만 성공시키면 인증 경계가 맞는지 알기 어렵다. 다음 상황을 함께 확인할 계획이다.
| 상황 | 기대하는 처리 |
|---|---|
| 다른 작업 공간 ID를 도구에 직접 입력 | 해당 사용자에게 권한이 없으면 거부 |
| 읽기 전용 연결로 수정 요청 | 사용자 본인이 편집자여도 연결 범위에서 차단 |
| 연결 후 사용자 권한 회수 | 이후 요청에는 변경된 권한 적용 |
| 키 폐기 또는 로그인 만료 | 인증 실패를 명확히 반환하고 재연결 안내 |
| 요청 도중 화면의 선택 대상 변경 | 실행 대상이 조용히 바뀌지 않도록 식별자와 확인 정보 유지 |
| 조회 결과에 다른 작업을 지시하는 문장 포함 | 데이터로 취급하고 권한 확대나 자동 승인에 사용하지 않음 |
정리
MCP와 WebMCP를 비교하면서 내린 결론은 “어느 쪽이 더 새롭거나 간단한가”보다 “요청 주체와 실행 환경을 어디서 관리할 것인가”가 먼저라는 것이다.
서버형 MCP는 브라우저 밖의 호출 경로를 열어 주지만 별도의 인증 및 자격 증명 관리가 필요하다. WebMCP는 웹페이지의 기능과 로그인 흐름을 활용할 수 있지만, 페이지 상태와 도구 실행에 대한 검증이 필요하다.
어느 쪽을 선택하더라도 에이전트가 할 수 있는 일은 사용자의 현재 권한과 그 연결에 허용한 범위를 넘지 않아야 한다. 도구 목록을 만드는 것보다 이 경계를 먼저 정리하는 것이 API를 여는 출발점이었다.
'Backend > Architecture' 카테고리의 다른 글
| [AI Agent] 서비스 안에 Agent를 넣을 때 Tool 실행과 비용 주체 나누기 (0) | 2026.10.04 |
|---|---|
| [Architecture] 엣지 서버의 로컬 ID와 전역 ID 충돌을 피하는 방법 (0) | 2026.09.20 |
| [Server] 사용자 JWT와 장비 Token의 인증 경계를 분리하기 (0) | 2026.09.19 |
| [Refactoring] 레거시 API를 이식할 때 계약과 버그를 구분하는 방법 (0) | 2026.09.13 |
| [Server] 대용량 ZIP을 생성하면서 API Gateway 너머로 스트리밍하기 (0) | 2026.09.12 |
| [Design Pattern] 서로 다른 데이터 스키마를 하나의 API로 다루기 (0) | 2026.08.18 |