- Published on
AutoGPT에 MCP로 사내툴 연결 - 권한·감사로그
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서내툴을 AutoGPT 같은 에이전트에 붙이는 순간, 모델 품질보다 더 큰 리스크가 먼저 튀어나옵니다. “누가 어떤 권한으로 어떤 데이터를 조회/변경했는가”, “에이전트가 호출한 도구 실행이 재현 가능한가”, “사고가 났을 때 원인을 끝까지 추적할 수 있는가”가 핵심입니다.
MCP(Model Context Protocol)는 에이전트가 외부 도구를 표준화된 방식으로 호출하도록 돕지만, 권한과 감사로그를 자동으로 해결해주지는 않습니다. 특히 사내툴은 HR/재무/고객정보처럼 민감 데이터가 섞여 있고, 변경 작업(승인, 배포, 계정 잠금 등)이 포함되기 때문에 권한 경계와 로그 설계가 없으면 운영 투입 자체가 어렵습니다.
아래에서는 AutoGPT agent 와 MCP server 를 기준으로, 사내툴 연결 시 권한·감사로그를 “설계 원칙 → 아키텍처 → 구현 예시 → 운영 체크리스트” 순서로 정리합니다.
관련해서 AutoGPT의 메모리 팽창과 환각을 줄이는 설계는 별도로 정리해둔 글이 있으니 함께 보면 전체 안정성이 좋아집니다: AutoGPT 메모리 팽창·환각 줄이는 RAG+벡터DB
MCP로 사내툴을 붙일 때 생기는 보안·감사 요구사항
1) 에이전트는 “사용자 대리인”인가, “시스템 작업자”인가
사내툴을 호출하는 주체를 두 가지로 나누면 설계가 쉬워집니다.
- 사용자 대리인(Delegation): 사용자가 가진 권한 범위 내에서만 에이전트가 도구를 실행합니다. 감사로그는 “사용자
U가 에이전트를 통해 실행”으로 남아야 합니다. - 시스템 작업자(Service Actor): 에이전트가 별도의 서비스 계정 권한으로 실행합니다. 대신 승인 워크플로우, 강한 정책, 제한된 스코프가 필요합니다.
대부분의 엔터프라이즈 환경에서는 기본값을 사용자 대리인으로 두고, 정말 필요한 자동화만 시스템 작업자로 분리합니다.
2) 권한은 인증(AuthN)보다 “인가(AuthZ)”에서 터진다
MCP 서버 앞단에 SSO를 붙여 “누구인지” 확인하는 것만으로는 부족합니다. 실제 사고는 다음에서 발생합니다.
- 에이전트가 “조회” 뿐 아니라 “변경” API까지 호출
- 사내툴의 RBAC이 조잡하거나, API 레벨 스코프가 없음
- 에이전트가 프롬프트 인젝션으로 다른 사람 데이터에 접근
즉, MCP 서버는 도구 호출 단위의 정책 집행 지점(PDP/PEP) 이 되어야 합니다.
3) 감사로그는 “사후 포렌식”이 아니라 “사전 억제” 장치다
감사로그가 제대로 설계되면 운영팀은 다음을 즉시 할 수 있습니다.
- 특정 사용자/에이전트/툴에 대한 호출 차단 및 롤백
- 모델 업데이트 이후 이상 호출 패턴 탐지
- 규제/감사 대응(누가, 언제, 무엇을, 왜)
따라서 로그는 단순 텍스트가 아니라 추적 가능한 구조화 이벤트여야 하고, 조작이 어려운 저장소로 흘러가야 합니다.
권한 아키텍처: “에이전트-툴” 사이에 무엇을 둬야 하나
아래는 실무에서 가장 안전하게 굴러가는 패턴입니다.
구성 요소
- AutoGPT/Agent Runtime: 사용자 요청을 받고, MCP 툴을 호출
- MCP Server(툴 게이트웨이): 툴 목록 제공 + 호출 라우팅 + 정책 집행 + 감사로그 발행
- Policy Engine: OPA, Cedar, Casbin 등(선택)
- Token Service(위임 토큰): 사용자 SSO 토큰을 그대로 쓰지 않고, 짧은 TTL의 도구용 토큰으로 교환
- Audit Log Pipeline: 불변 저장(예: WORM 버킷, SIEM)
핵심 원칙 5가지
- 최소권한(Least Privilege): 툴별로 읽기/쓰기/승인 권한을 분리
- 짧은 TTL + 스코프 토큰: 5~15분 만료, 툴/리소스/액션 스코프 포함
- 정책은 코드로(Policy as Code): “프롬프트”가 아니라 정책으로 막기
- 변경 작업은 2단계(Prepare/Commit): 에이전트 단독으로 커밋 금지
- 감사로그는 동기 강제, 상세는 비동기: 최소 이벤트는 반드시 남기고, 상세 페이로드는 별도 채널로
구현 패턴 1: 사용자 위임(Delegation) + 스코프 토큰
흐름
- 사용자가 사내 포털에서 AutoGPT를 사용(SSO)
- 에이전트가 MCP 서버에 툴 호출 요청
- MCP 서버가 사용자 컨텍스트를 확인하고, 토큰 서비스에서 위임 토큰을 발급
- MCP 서버가 사내툴 API 호출
- 결과와 함께 감사 이벤트 발행
토큰 설계 팁
sub: 사용자 IDact: acting agent ID(또는 client ID)scope:tool:hr.read,tool:ticket.write같은 형태res: 리소스 범위(팀, 프로젝트, tenant)jti: 토큰 고유 ID(재사용 탐지)
아래는 예시 JWT 클레임(개념)입니다.
{
"iss": "token-service",
"sub": "user:kim",
"act": "agent:autogpt-prod",
"aud": "mcp-server",
"scope": ["tool:ticket.read", "tool:ticket.comment"],
"res": {"project": "PAY", "team": "platform"},
"exp": 1730000000,
"jti": "b3f1d2..."
}
구현 패턴 2: 정책 엔진(OPA)로 툴 호출 단위 인가
MCP 서버는 툴 호출을 받는 순간, “이 사용자가 이 툴의 이 액션을 이 리소스에 대해 실행할 수 있는가”를 판정해야 합니다.
OPA(Rego) 예시
아래는 ticket.comment 는 허용하되, ticket.close 는 role 이 lead 이상일 때만 허용하는 간단한 정책입니다.
package mcp.authz
default allow = false
allow {
input.tool == "ticket"
input.action == "comment"
input.user.active == true
}
allow {
input.tool == "ticket"
input.action == "close"
input.user.role in {"lead", "manager", "admin"}
}
MCP 서버는 툴 호출마다 다음 입력을 만들어 OPA에 질의합니다.
{
"tool": "ticket",
"action": "close",
"user": {"id": "user:kim", "role": "member", "active": true},
"resource": {"project": "PAY", "ticketId": "TCK-123"},
"context": {"agentId": "agent:autogpt-prod"}
}
이 방식의 장점은 “프롬프트가 무엇을 말하든” 정책이 최종 결정을 내린다는 점입니다.
구현 패턴 3: 변경 작업은 prepare 와 commit 으로 쪼개기
에이전트가 가장 위험한 일을 하는 순간은 “쓰기/삭제/승인”입니다. 여기서 추천하는 패턴은 2단계 커밋입니다.
prepare: 에이전트가 변경안을 만들고, 영향 범위와 diff를 생성commit: 사람이 승인하거나, 별도 승인 정책을 통과해야 실행
예시: 배포 승인 툴
deploy.prepare(service, version)는 누구나 가능(권한 낮음)deploy.commit(planId)는 승인자만 가능(권한 높음)
이 구조는 감사로그에도 유리합니다. “왜 배포됐는지”가 plan과 diff로 남기 때문입니다.
감사로그 설계: 무엇을, 어떤 형태로, 어디에 남길까
로그 이벤트 스키마(권장)
감사 이벤트는 최소한 아래 필드를 가져야 합니다.
timestamprequest_id/trace_idactor.user_id(실사용자)actor.agent_id(에이전트/클라이언트)tool.name,tool.actionresource(가능하면 정규화)decision(allow/deny + 이유)input_hash/output_hash(민감정보 원문 대신 해시)policy_versiontoken_jtilatency_ms,status_code
예시 JSON(감사 이벤트)
{
"timestamp": "2026-02-26T10:12:34.123Z",
"trace_id": "trc_01H...",
"request_id": "req_9f2...",
"actor": {
"user_id": "user:kim",
"agent_id": "agent:autogpt-prod"
},
"tool": {
"name": "ticket",
"action": "comment",
"version": "2026.02.1"
},
"resource": {
"project": "PAY",
"ticket_id": "TCK-123"
},
"decision": {
"result": "allow",
"policy": "opa",
"policy_version": "git:8c1a2b3",
"reason": "role=member allowed for comment"
},
"io": {
"input_hash": "sha256:...",
"output_hash": "sha256:..."
},
"token_jti": "b3f1d2...",
"http": {
"status_code": 200,
"latency_ms": 183
}
}
민감정보는 어떻게 다루나
감사로그에 원문을 다 넣으면 보안 사고가 됩니다. 반대로 아무것도 안 남기면 포렌식이 불가능합니다. 절충안은 다음입니다.
- 원문은 저장하지 않고 해시: 동일 요청 재현/중복 탐지에 유리
- 필드 단위 마스킹/토큰화: 이메일, 주민번호, 계좌 등
- 샘플링 금지: 감사로그는 샘플링하면 안 됩니다(관측성 로그와 다름)
- 별도 보관소에 암호화 저장: 정말 필요한 원문은 KMS로 암호화해 분리 저장하고 접근을 엄격히 통제
MCP 서버 구현 예시: FastAPI 기반 게이트웨이
아래 코드는 “툴 호출 전 정책 검사 → 사내툴 호출 → 감사 이벤트 발행”의 골격을 보여줍니다.
주의할 점은 본문에 -> 같은 기호를 그대로 쓰면 안 되므로, 코드 블록 안에서만 사용합니다.
from fastapi import FastAPI, Request, HTTPException
import time
import hashlib
import json
app = FastAPI()
# 예시: OPA 질의 함수(실제로는 HTTP로 OPA에 query)
def opa_allow(input_doc: dict) -> tuple[bool, str]:
# 데모용: comment는 허용, close는 role=lead 이상만 허용
if input_doc["action"] == "comment":
return True, "allowed: comment"
if input_doc["action"] == "close" and input_doc["user"]["role"] in {"lead", "manager", "admin"}:
return True, "allowed: close by role"
return False, "denied by policy"
def sha256_hex(obj: dict) -> str:
raw = json.dumps(obj, sort_keys=True, ensure_ascii=False).encode("utf-8")
return hashlib.sha256(raw).hexdigest()
@app.post("/mcp/tools/ticket/{action}")
async def ticket_tool(action: str, request: Request):
started = time.time()
body = await request.json()
user = body.get("user")
resource = body.get("resource")
payload = body.get("payload")
if not user:
raise HTTPException(status_code=401, detail="missing user context")
input_doc = {
"tool": "ticket",
"action": action,
"user": {"id": user["id"], "role": user.get("role", "member"), "active": True},
"resource": resource or {},
"context": {"agentId": body.get("agent_id", "unknown")}
}
allow, reason = opa_allow(input_doc)
audit_event = {
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"trace_id": body.get("trace_id"),
"actor": {"user_id": user["id"], "agent_id": body.get("agent_id")},
"tool": {"name": "ticket", "action": action},
"resource": resource or {},
"decision": {"result": "allow" if allow else "deny", "reason": reason},
"io": {
"input_hash": f"sha256:{sha256_hex({"payload": payload, "resource": resource})}"
}
}
if not allow:
# 여기서도 감사 이벤트는 반드시 남긴다
emit_audit(audit_event)
raise HTTPException(status_code=403, detail="forbidden")
# 실제 사내툴 호출(여기서는 더미)
result = {"ok": True, "action": action}
audit_event["io"]["output_hash"] = f"sha256:{sha256_hex(result)}"
audit_event["http"] = {"status_code": 200, "latency_ms": int((time.time() - started) * 1000)}
emit_audit(audit_event)
return result
def emit_audit(event: dict) -> None:
# 실무에서는 Kafka/OTel Collector/SIEM 등으로 전송
print(json.dumps(event, ensure_ascii=False))
이 코드에서 중요한 포인트는 다음입니다.
deny도 무조건 로깅- 입력/출력은 해시로 남김
- 정책 결정 이유(
reason)를 남겨 운영자가 디버깅 가능
프롬프트 인젝션과 “권한 우회”를 막는 실전 장치
MCP로 사내툴을 붙이면 프롬프트 인젝션이 곧바로 “데이터 유출/변경”으로 이어질 수 있습니다. 아래는 효과가 큰 방어선입니다.
1) 툴 스키마를 최소화하고, 파라미터를 강제 검증
- 자유 텍스트로
sql이나url을 받는 툴은 금지에 가깝게 취급 - 가능한 한
enum과 정규화된 식별자만 받기
2) 리소스 스코프를 토큰에 넣고 서버에서 재검증
클라이언트가 보내는 project=PAY 같은 값은 신뢰하면 안 됩니다. 토큰의 res 와 대조해야 합니다.
3) “툴 호출 횟수/속도 제한”도 권한의 일부
에이전트는 실수로 루프를 돌 수 있습니다. 다음을 권장합니다.
- 사용자별
rate limit - 툴별
quota - 고비용 작업(대량 export)은 별도 승인
운영 체크리스트: 배포 전에 반드시 확인할 것
권한
- 툴별
read/write/approve가 분리되어 있는가 - 위임 토큰 TTL이 짧고, 재사용 탐지(
jti)가 가능한가 - 정책 변경이 코드리뷰/승인 흐름을 타는가
감사로그
allow와deny모두 남는가- 최소 필드(누가/무엇을/어디에/결정/정책버전)가 빠지지 않는가
- 로그 저장소가 삭제/수정에 강한가(WORM 또는 SIEM)
- 개인정보/비밀키/토큰 원문이 로그에 남지 않는가
장애 대응
- 정책 엔진 장애 시 기본값이
deny인가 - 감사 파이프라인 장애 시 “툴 호출을 막을지/허용하되 로컬 버퍼링할지” 원칙이 정해졌는가
클라우드 환경에서 권한 문제가 실제로 어떻게 터지는지 감을 잡으려면, STS 권한 이슈를 다룬 글도 참고할 만합니다: EKS Pod에서 STS 403 AccessDenied 원인 8가지
마무리: MCP 연결의 성공 기준은 “안전하게 자동화되었는가”
AutoGPT에 MCP로 사내툴을 붙이는 프로젝트는, 데모는 빠르게 나오지만 운영은 권한과 감사로그에서 갈립니다. 정리하면 다음 3가지만 지키면 성공 확률이 크게 올라갑니다.
- 에이전트 호출을 사용자 위임으로 모델링하고, 스코프 토큰으로 좁힌다
- MCP 서버를 정책 집행 지점으로 두고, Policy as Code로 통제한다
- 감사로그를 구조화 + 불변성으로 설계해 재현성과 책임추적성을 확보한다
이 기반 위에 RAG, 메모리 전략, 툴 카탈로그 확장 등을 얹으면 “쓸 수 있는 사내 에이전트”로 진화합니다.